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

file-headers.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 <config.h>

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>

#include <gmime/gmime.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/file-headers.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/group.h>
#include <pan/base/log.h>
#include <pan/base/status-item.h>
#include <pan/base/util-file.h>

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

typedef struct
{
      Group * folder;
      GPtrArray * articles;
}
FolderForeachStruct;

static void
file_headers_load_folder_foreach (GMimeMessage * message, gpointer user_data)
{
      Article * article;
      FolderForeachStruct * ffs = (FolderForeachStruct*) user_data;

      article = article_new (ffs->folder);
      article_set_from_g_mime_message (article, message);
      article->number = ffs->articles->len + 1;
      g_ptr_array_add (ffs->articles, article);
}

static gboolean
file_headers_load_folder (Group * folder, StatusItem * status, guint * article_qty)
{
      FolderForeachStruct ffs;
      debug_enter ("file_headers_load_folder");

      /* sanity clause */
      g_return_val_if_fail (group_is_valid(folder), FALSE);
      g_return_val_if_fail (group_is_folder(folder), FALSE);

      /* prep for foreach */
      ffs.folder = folder;
      ffs.articles = g_ptr_array_new ();
      acache_path_foreach (folder->name.str, file_headers_load_folder_foreach, &ffs);

      /* if we've got articles then add them; otherwise; clean up */
      if (ffs.articles->len != 0)
            group_init_articles (folder, ffs.articles, status);
      else if (folder->_articles) {
            g_hash_table_destroy (folder->_articles);
            folder->_articles = NULL;
      }
      folder->articles_dirty = FALSE;

      *article_qty = ffs.articles->len;
      g_ptr_array_free (ffs.articles, TRUE);
      debug_exit ("file_headers_load_folder");
      return folder->_articles!=NULL && g_hash_table_size(folder->_articles)!=0;
}

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

static void
purge_0140_style_files (const Group * group)
{
      char fname [PATH_MAX];

      g_return_if_fail (group_is_valid (group));

      g_snprintf (fname, sizeof(fname), "%s%c%*.*s%c%*.*s.idx",
                  get_data_dir(),
                  G_DIR_SEPARATOR, group->server->name.len, group->server->name.len, group->server->name.str,
                  G_DIR_SEPARATOR, group->name.len, group->name.len, group->name.str);
      unlink (fname);

      g_snprintf (fname, sizeof(fname), "%s%c%*.*s%c%*.*s.dat",
                  get_data_dir(),
                  G_DIR_SEPARATOR, group->server->name.len, group->server->name.len, group->server->name.str,
                  G_DIR_SEPARATOR, group->name.len, group->name.len, group->name.str);
      unlink (fname);
}

static void
purge_014090_style_files (const Group * group)
{
      char fname [PATH_MAX];

      g_return_if_fail (group_is_valid (group));

      g_snprintf (fname, sizeof(fname), "%s%c%*.*s%c%*.*s",
                  get_data_dir(),
                  G_DIR_SEPARATOR, group->server->name.len, group->server->name.len, group->server->name.str,
                  G_DIR_SEPARATOR, group->name.len, group->name.len, group->name.str);
      unlink (fname);
}

static gboolean
headers_0140_style_exists (const Group * group)
{
        char fname [PATH_MAX];

        g_return_val_if_fail (group_is_valid (group), FALSE);

      g_snprintf (fname, sizeof(fname), "%s%c%*.*s%c%*.*s.idx",
                  get_data_dir(),
                  G_DIR_SEPARATOR, group->server->name.len, group->server->name.len, group->server->name.str,
                  G_DIR_SEPARATOR, group->name.len, group->name.len, group->name.str);

        return pan_file_exists  (fname);
}

static gboolean
file_headers_load_group_0140 (Group * group, StatusItem * status, guint * article_qty)
{
      const PString * sname;
      const PString * gname;
      gboolean success = FALSE;
      char * dat = NULL;
      char * idx = NULL;
      size_t dat_len = 0;
      size_t idx_len = 0;
      char path[PATH_MAX];
      debug_enter ("file_headers_load_group_0140");

      g_return_val_if_fail (group_is_valid (group), FALSE);

      if (status!=NULL)
            status_item_emit_status_va (status, _("Loading group \"%s\""), group_get_name(group));

      sname = &group->server->name;
      gname = &group->name;

      /* open the index file */
      g_snprintf (path, sizeof(path), "%s%c%*.*s%c%*.*s.idx",
                  get_data_dir(),
                  G_DIR_SEPARATOR, sname->len, sname->len, sname->str,
                  G_DIR_SEPARATOR, gname->len, gname->len, gname->str);
      g_file_get_contents (path, &idx, &idx_len, NULL);

      /* open the data file */
      g_snprintf (path, sizeof(path), "%s%c%*.*s%c%*.*s.dat",
                  get_data_dir(),
                  G_DIR_SEPARATOR, sname->len, sname->len, sname->str,
                  G_DIR_SEPARATOR, gname->len, gname->len, gname->str);
      g_file_get_contents (path, &dat, &dat_len, NULL);

      if (dat!=NULL && idx!=NULL)
      {
            const char * march = idx;
            const glong version = get_next_token_int (march, '\n', &march);

            if (1<=version && version<=10)
            {
                  int i;
                  int purged_article_count = 0;
                  long l;
                  const long qty = get_next_token_long (march, '\n', &march);
                  GPtrArray * addme;

                  /* load the articles */
                        addme = g_ptr_array_sized_new (qty);
                  for (i=0; i!=qty; ++i)
                  {
                        Article * a = article_new (group);

                        /* message id */
                        l = get_next_token_long (march, '\n', &march);
                        if (0<=l && l<dat_len) {
                              const PString message_id = pstring_shallow (dat+l, -1);
                              article_set_message_id (a, &message_id);
                        }

                        /* author */
                        if (version<2) /* version 2 split author into 2 fields */
                        {
                              l = get_next_token_long (march, '\n', &march);
                              if (0<=l && l<dat_len) {
                                    const PString author = pstring_shallow (dat+l, -1);
                                    article_set_author (a, &author);
                              }
                        }
                        else
                        {
                              l = get_next_token_long (march, '\n', &march);
                              if (0<=l && l<dat_len)
                                    pstring_set (&a->author_addr, dat+l, -1);

                              l = get_next_token_long (march, '\n', &march);
                              if (0<=l && l<dat_len)
                                    pstring_set (&a->author_real, dat+l, -1);
                        }

                        /* subject */
                        l = get_next_token_long (march, '\n', &march);
                        if (0<=l && l<dat_len) {
                              const PString subject = pstring_shallow (dat+l, -1);
                              article_set_subject (a, &subject);
                        }

                        /* date string - removed in version 3 */
                        if (version<3)
                              skip_next_token (march, '\n', &march);

                        /* references */
                        l = get_next_token_long (march, '\n', &march);
                        if (0<=l && l<dat_len) {
                              const PString references = pstring_shallow (dat+l, -1);
                              article_set_references (a, &references);
                        }

                        /* xrefs added in version 4 */
                        if (version>=4) {
                              l = get_next_token_long (march, '\n', &march);
                              if (0<=l && l<dat_len) {
                                    const PString xref = pstring_shallow (dat+l, -1);
                                    article_set_xref (a, &xref);
                              }
                        }

                        /* numeric fields */
                        a->part           = (gint16) get_next_token_int (march, '\n', &march);
                        a->parts          = (gint16) get_next_token_int (march, '\n', &march);
                        a->linecount      = (guint16) get_next_token_int (march, '\n', &march);

                        if (version>=9)
                              a->byte_qty = (gulong) get_next_token_ulong (march, '\n', &march);

                        /* crosspost_qty - removed in version 6 */
                        if (version<6)
                              skip_next_token (march, '\n', &march);

                        /* state - removed in version 6, back in 7, removed in 10 */
                        if (version!=6 && version<10)
                              skip_next_token (march, '\n', &march);

                        a->date           = (time_t) get_next_token_ulong (march, '\n', &march);
                        a->number         = (gulong) get_next_token_ulong (march, '\n', &march);

                        if (1) {
                              /* an article is new if it's flagged as new AND it's unread */
                              gboolean b = FALSE;
                              if (version >= 5)
                                    b = get_next_token_int (march, '\n', &march) != 0;
                              if (b)
                                    b = !article_is_read (a);
                              a->is_new = b;
                        }

                        /* extra headers removed in version 8 */
                        if (version<8)
                              skip_next_token (march, '\n', &march);

                        /* let the user know what we're doing */
                        if (status != NULL) {
                              status_item_emit_next_step (status);
                              if (!(addme->len % 256))
                                    status_item_emit_status_va (status,
                                          _("Loaded %d of %d articles"), i, qty);
                        }

                        /* add the article to the group if it looks sane */
                        if (article_is_valid (a))
                              g_ptr_array_add (addme, a);
                        else
                              ++purged_article_count;
                  }

                  if (purged_article_count != 0)
                  {
                        log_add_va (LOG_ERROR,
                                  _("Pan skipped %d corrupt headers from the local cache for group \"%*.*s\"."),
                                  purged_article_count,
                                    group->name.len, group->name.len, group->name.str);
                        log_add (LOG_ERROR,
                               _("You may want to empty this group and download fresh headers."));
                  }

                  status_item_emit_status_va (status, _("Loaded %d of %d articles"), i, qty);

                  *article_qty = addme->len;
                  group_init_articles (group, addme, status);
                  group->articles_dirty = purged_article_count!=0;
                  success = TRUE;
                  g_ptr_array_free (addme, TRUE);
            }
            else
            {
                  log_add_va (LOG_ERROR|LOG_URGENT,
                        _("Unsupported data version for %s headers: %d.\nAre you running an old version of Pan by accident?"), group->name, version);
            }
      }

      /* cleanup */
      g_free (idx);
      g_free (dat);

      debug_exit ("file_headers_load_group_0140");
      return success;
}

static void
get_014090_filename (char * buf, int buf_len, const Group * group)
{
      const PString * sname = &group->server->name;
      const PString * gname = &group->name;

      g_snprintf (buf, buf_len, "%s%c%*.*s%c%*.*s",
                  get_data_dir(),
                  G_DIR_SEPARATOR, sname->len, sname->len, sname->str,
                  G_DIR_SEPARATOR, gname->len, gname->len, gname->str);
}

static gboolean
headers_014090_style_exists (const Group * group)
{
        gchar fname [PATH_MAX];
        g_return_val_if_fail (group_is_valid (group), FALSE);

      get_014090_filename (fname, sizeof(fname), group);
        return pan_file_exists  (fname);
}

static gboolean
file_headers_load_group_014090 (Group * group, StatusItem * status, guint * article_qty)
{
      GIOChannel * in_gio;
      char in_fname[PATH_MAX];
      gboolean success = FALSE;
      debug_enter ("file_headers_load_group_014090");

      g_return_val_if_fail (group_is_valid (group), FALSE);

      if (status!=NULL)
            status_item_emit_status_va (status, _("Loading group \"%s\""), group_get_name(group));

      /* open the group's file */
      get_014090_filename (in_fname, sizeof(in_fname), group);
      in_gio = g_io_channel_new_file (in_fname, "r", NULL);

      if (in_gio != NULL)
      {
            int version;
            GString * line;

            g_io_channel_set_encoding (in_gio, NULL, NULL);
            line = g_string_sized_new (1024);

            g_io_channel_read_line_string (in_gio, line, NULL, NULL);
            version = atoi (line->str);
            if (version==11)
            {
                  int i;
                  int purged_article_count = 0;
                  long qty;
                  GPtrArray * addme;

                  /* quantity of articles */
                  g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                  qty = strtol (line->str, NULL, 10);

                  /* load the articles */
                        addme = g_ptr_array_sized_new (qty);
                  for (i=0; i!=qty; ++i)
                  {
                        Article * a = article_new (group);

#ifdef G_OS_WIN32
#define EOLN_LEN 2 /* \r\n */
#else
#define EOLN_LEN 1 /* \n */
#endif 

#define set_string_from_next_line(pstr) \
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL); \
                        g_string_truncate (line, line->len-EOLN_LEN); \
                        if (line->len) \
                              pstring_set (pstr, line->str, line->len);
                        set_string_from_next_line (&a->message_id)
                        set_string_from_next_line (&a->author_addr)
                        set_string_from_next_line (&a->author_real)
                        set_string_from_next_line (&a->subject)
                        set_string_from_next_line (&a->references)
                        set_string_from_next_line (&a->xref)
#undef set_string_from_next_line

                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->part = (gint16) atoi (line->str);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->parts = (gint16) atoi (line->str);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->linecount = (guint16) atoi (line->str);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->byte_qty = strtoul (line->str, NULL, 10);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->date = (time_t) strtoul (line->str, NULL, 10);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->number = strtoul (line->str, NULL, 10);
                        g_io_channel_read_line_string (in_gio, line, NULL, NULL);
                        a->is_new = atoi (line->str) != 0;

                        /* let the user know what we're doing */
                        if (status != NULL) {
                              status_item_emit_next_step (status);
                              if (!(addme->len % 512))
                                    status_item_emit_status_va (status,
                                          _("Loaded %d of %d articles"), i, qty);
                        }

                        /* add the article to the group if it looks sane */
                        if (article_is_valid (a))
                              g_ptr_array_add (addme, a);
                        else
                              ++purged_article_count;
                  }

                  if (purged_article_count != 0)
                  {
                        log_add_va (LOG_ERROR,
                                    _("Pan skipped %d corrupt headers from the local cache for group \"%*.*s\"."),
                                    purged_article_count,
                                    group->name.len, group->name.len, group->name.str);
                        log_add (LOG_ERROR,
                                 _("You may want to empty this group and download fresh headers."));
                  }

                  *article_qty = addme->len;
                  group_init_articles (group, addme, status);
                  group->articles_dirty = purged_article_count!=0;
                  success = TRUE;
                  g_ptr_array_free (addme, TRUE);
            }
            else
            {
                  log_add_va (LOG_ERROR|LOG_URGENT,
                        _("Unsupported data version for %s headers: %d.\nAre you running an old version of Pan by accident?"), group->name, version);
            }

            /* cleanup */
            g_string_free (line, TRUE);
            g_io_channel_unref (in_gio);
      }

      /* cleanup */
      debug_exit ("file_headers_load_group_014090");
      return success;
}


void
file_headers_load (Group * group, StatusItem * status)
{
      gboolean success = FALSE;
      guint article_qty = 0u;
      double diff;
      GTimeVal start;
      GTimeVal finish;
      debug_enter ("file_headers_load");

      /* start the stopwatch */
      g_get_current_time (&start);

      /* load the group */
      if (group_is_folder (group))
            success = file_headers_load_folder (group, status, &article_qty);
      else if (headers_014090_style_exists (group))
            success = file_headers_load_group_014090 (group, status, &article_qty);
      else if (headers_0140_style_exists (group))
            success = file_headers_load_group_0140 (group, status, &article_qty);

      /* expire the old articles, if any */
      if (success)
      {
            gulong low=0, high=0;
            group_get_article_range (group, &low, &high);
            group_expire_articles_not_in_range (group, low, high);
      }

      /* timing stats */
      g_get_current_time (&finish);
      diff = finish.tv_sec - start.tv_sec;
      diff += (finish.tv_usec - start.tv_usec)/(double)G_USEC_PER_SEC;
      if (article_qty != 0u)
            log_add_va (LOG_INFO, _("Loaded %u articles for group \"%s\" in %.1f seconds (%.0f art/sec)"),
                                  article_qty,
                                  group_get_name(group),
                                  diff,
                                  article_qty/(fabs(diff)<0.001?0.001:diff));

      debug_exit ("file_headers_load");
}

typedef struct
{
      StatusItem * status;
      gboolean success;
}
FileHeadersSaveData;

static void
file_headers_save_group_articles (Group              * group,
                                  Article           ** articles,
                                  guint                article_qty,
                                  gpointer             user_data)
{
      FileHeadersSaveData * data = (FileHeadersSaveData*) user_data;
      guint i;
      FILE * out_fp;
      char * dir;
      char out_fname [PATH_MAX];
      char out_fname_tmp [PATH_MAX];
      gboolean ok = TRUE;
      debug_enter ("file_headers_save_group");

      if (!article_qty)
      {
            file_headers_destroy (group);
            data->success = FALSE;
            return;
      }

      /* prep the directory & filenames */
      get_014090_filename (out_fname, sizeof(out_fname), group);
      g_snprintf (out_fname_tmp, sizeof(out_fname_tmp), "%s.tmp", out_fname);
      dir = g_path_get_dirname (out_fname);
      pan_file_ensure_path_exists (dir);
      g_free (dir);

      /* open temporary output file */
      out_fp = fopen (out_fname_tmp, "w+");
      if (out_fp == NULL)
      {
            log_add_va (LOG_ERROR, _("The group will not be saved -- can't create file \"%s\""), out_fname_tmp);
            data->success = FALSE;
            return;
      }

      /* write file format and number of headers */
      fprintf (out_fp, "11\n%ld\n", (long)article_qty);

      /* write the header information */
      for (i=0; i!=article_qty; ++i)
      {
            const Article * a = articles[i];

            pan_warn_if_fail (a->number != 0);

            /* write the non-string fields. */
            fprintf (out_fp,
                  "%*.*s\n" /* message-id */
                  "%*.*s\n" /* author addr */
                  "%*.*s\n" /* author real */
                  "%*.*s\n" /* subject */
                  "%*.*s\n" /* references */
                  "%*.*s\n" /* xref */
                  "%d\n" "%d\n" /* part, parts */
                  "%u\n" /* linecount */
                  "%lu\n" /* byte qty */
                  "%lu\n" /* date */
                  "%lu\n" /* number */
                  "%d\n", /* is_new */
                  a->message_id.len,  a->message_id.len,  (a->message_id.str  ? a->message_id.str  : ""),
                  a->author_addr.len, a->author_addr.len, (a->author_addr.str ? a->author_addr.str : ""),
                  a->author_real.len, a->author_real.len, (a->author_real.str ? a->author_real.str : ""),
                  a->subject.len,     a->subject.len,     (a->subject.str     ? a->subject.str     : ""),
                  a->references.len,  a->references.len,  (a->references.str  ? a->references.str  : ""),
                  a->xref.len,        a->xref.len,        (a->xref.str        ? a->xref.str        : ""),
                  (int)a->part, (int)a->parts,
                  (unsigned int)a->linecount,
                  (unsigned long)a->byte_qty,
                  (unsigned long)a->date,
                  (unsigned long)a->number,
                  (int)(a->is_new != 0));
      }

      /* did the files write out okay? */
      ok = !ferror(out_fp);

      /* close the temp files */
      fclose (out_fp);

      if (!ok)
      {
            log_add_va (LOG_ERROR, _("Unable to save headers for group \"%*.*s\" - is the disk full?"),
                                    group->name.len, group->name.len, group->name.str);
            unlink (out_fname_tmp);
      }

      if (ok)
      {
            ok = pan_file_rename (out_fname_tmp, out_fname);
      }

      if (ok)
      {
            purge_0140_style_files (group);
      }

      /* cleanup */
      debug_exit ("file_headers_save_group");
      data->success = ok;
}

static gboolean
file_headers_save_group (Group * group, StatusItem * status)
{
      FileHeadersSaveData data;
      data.status = status;
      data.success = FALSE;
      group_article_forall (group, file_headers_save_group_articles, &data);
      return data.success;
}

static void
file_headers_save_articles (Group * group, Article ** articles, guint article_qty, gpointer user_data)
{
      StatusItem * status = (StatusItem*) user_data;

      if (!article_qty)
      {
            file_headers_destroy (group);
      }
      else if (!group_is_folder (group)) /* save the group */
      {
            GTimeVal start;
            GTimeVal finish;
            double diff;

            /* start the stopwatch */
            g_get_current_time (&start);

            /* save the group */
            if (!file_headers_save_group (group, status))
                  group->articles_dirty = TRUE;
            else {
                  /* end the stopwatch */
                  g_get_current_time (&finish);
                  diff = finish.tv_sec - start.tv_sec;
                  diff += (finish.tv_usec - start.tv_usec)/(double)G_USEC_PER_SEC;
                  log_add_va (LOG_INFO, _("Saved %d articles in \"%s\" in %.1f seconds (%.0f art/sec)"),
                        article_qty,
                        group_get_name (group),
                        diff,
                        article_qty/(fabs(diff)<0.001?0.001:diff));
            }
      }
}
void
file_headers_save_noref (Group * group, StatusItem * status)
{
      debug_enter ("file_headers_save_noref");

      if (group->articles_dirty)
      {
            group->articles_dirty = FALSE;
            group_article_forall (group, file_headers_save_articles, status);
      }

      debug_exit ("file_headers_save_noref");
}
void
file_headers_save (Group * group, StatusItem * status)
{
      debug_enter ("file_headers_save");
      g_return_if_fail (group!=NULL);

      group_ref_articles (group, status);
      file_headers_save_noref (group, status);
      group_unref_articles (group, status);

      debug_exit ("file_headers_save");
}

void
file_headers_destroy (const Group * group)
{
      debug_enter ("file_headers_destroy");

      g_return_if_fail (group_is_valid (group));

      if (!group_is_folder(group))
      {
            purge_0140_style_files (group);
            purge_014090_style_files (group);
      }

      debug_exit ("file_headers_destroy");
}

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

void
file_headers_server_name_changed  (const PString * old_name,
                                   const PString * new_name)
{
      char * old_path;

      /* sanity clause */
      g_return_if_fail (pstring_is_set (old_name));
      g_return_if_fail (pstring_is_set (new_name));

      /* rename the server's datafile directory */
      old_path = g_build_filename (get_data_dir(), old_name->str, NULL);
      if (g_file_test (old_path, G_FILE_TEST_IS_DIR))
      {
            char * new_path = g_build_filename (get_data_dir(), new_name->str, NULL);
            pan_file_rename (old_path, new_path);
            g_free (new_path);
      }

      /* cleanup */
      g_free (old_path);
}

Generated by  Doxygen 1.6.0   Back to index