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

newsrc.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 <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <glib.h>

#include <pan/base/debug.h>
#include <pan/base/newsrc.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-object.h>

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

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

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

typedef struct
{
      gulong low;
      gulong high;
}
Range;

struct _Newsrc
{
      /* parent class */
      PanObject parent;

      /* private fields */
      gulong group_low; /* the low article number in the group */
      gulong group_high;      /* the high article number in the group */
      GArray * read;    /* sorted array of ranges of read article #s */
      char * read_str;  /* the .newsrc string of read article #s */
};

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

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

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

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

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

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

/************
*************  PRIVATE
************/

/*****
******  INIT READ
*****/

static void
parse_read_str (Newsrc         * n,
                const char     * str,
                GArray         * setme_read)
{
      const char * pch = str;

      g_return_if_fail (setme_read != NULL);

      if (is_nonempty_string(str))
      {
            gboolean error = FALSE;
            while (*pch && !error)
            {
                  gulong high;
                  gulong low;

                  while (*pch && isspace((guchar)*pch)) ++pch;
                  low = isdigit((guchar)*pch) ? strtoul (pch,(char**)&pch,10) : 0;
                  switch (*pch)
                  {
                        case ',' : /* single article */
                              high = low;
                              ++pch;
                              break;
                        case '\0' : /* single article at end of line */
                              high = low;
                              break;
                        case '-' : /* start of a range */
                              ++pch;
                              while (*pch && isspace((guchar)*pch)) ++pch;
                              if (!isdigit((guchar)*pch))
                                    high = ULONG_MAX;
                              else {
                                    high = strtoul (pch, (char**)&pch, 10);
                                    if (*pch == ',')
                                          ++pch;
                              }
                              break;
                        default: /* some invalid range? */
                              high = low;
                              error = TRUE;
                              pan_warn_if_reached ();
                              g_message ("the line was [%s] end is [%s]", str, pch);
                              break;
                  }

                  if (!error)
                  {
                        newsrc_mark_range (n, low, high, TRUE);
                  }
            }
      }
}

/**
 * This makes sure that n->read exists.  If it doesn't already exist,
 * it's created based on the information found in n->read_str.
 */
static void
init_read (Newsrc * n)
{
      g_return_if_fail (n != NULL);
      g_return_if_fail (n->read == NULL);

      n->read = g_array_new (FALSE, FALSE, sizeof(Range));
      parse_read_str (n, n->read_str, n->read);
}

/*****
******  RANGE UTIL
*****/

static int
compare_pulong_to_pRange (const void * a, const void * b)
{
      register const gulong number = *(gulong *)a;
      register const Range * range = (const Range*) b;
      int retval;

      if (number < range->low)
            retval = -1;
      else if (number > range->high)
            retval = 1;
      else
            retval = 0;

      return retval;
}

static gboolean
range_contains (const Range * r1, const Range * r2)
{
      return r2->low >= r1->low && r2->high <= r1->high;
}

static gboolean
range_contains_point (const Range * r1, gulong point)
{
      return r1->low<=point && point<=r1->high;
}

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

static gboolean
maybe_merge_ranges (GArray * a, int low)
{
      gboolean merged = FALSE;

      if (0<=low && low<=(int)a->len-2)
      {
            Range * r1 = &g_array_index (a, Range, low);
            Range * r2 = &g_array_index (a, Range, low+1);
            if (r1->high+1 == r2->low)
            {
                  r2->low = r1->low;
                  g_array_remove_index (a, low);
                  merged = TRUE;
            }
      }

      return merged;
}

/**
 * @return the number of articles newly-marked as read 
 */
static gulong
mark_range_read (Newsrc * n, Range * rr)
{
      GArray * a = n->read;
      int i;
      int low_index = 0;
      int high_index = 0;
      gulong retval = 0;
      gboolean range_found = FALSE;

      low_index = lower_bound (&rr->low,
                               a->data,
                               a->len,
                               sizeof(Range),
                               compare_pulong_to_pRange,
                               NULL);

      high_index = low_index + lower_bound (&rr->high,
                                            (Range*)a->data+low_index,
                                            a->len-low_index,
                                            sizeof(Range),
                                            compare_pulong_to_pRange,
                                            NULL);

      retval = rr->high + 1 - rr->low;

      for (i=low_index; i<=high_index && i<a->len; ++i)
      {
            Range * r = &g_array_index (a, Range, i);

            if (range_contains (rr, r)) /* read range is engulfed; remove */
            {
                  retval -= r->high+1 - r->low; /* remove r from retval */
                  g_array_remove_index (a, i);
                  --high_index;
                  --i;
            }
            else if (range_contains (r, rr)) /* no-op */
            {
                  retval = 0;
                  range_found = TRUE;
            }
            else if (range_contains_point (r, rr->high)) /* change low */
            {
                  Range * prev = !i ? NULL : &g_array_index (a, Range, i-1);
                  range_found = TRUE;
                  retval -= rr->high+1 - r->low; /* remove intersection from retval */
                  r->low = prev ? MAX(rr->low, prev->high+1) : rr->low;
            }
            else if (range_contains_point (r, rr->low)) /* change high */
            {
                  Range * next = i==a->len-1 ? NULL : &g_array_index (a, Range, i+1);
                  range_found = TRUE;
                  retval -= r->high+1 - rr->low; /* remove intersection from retval */
                  r->high = next ? MIN(rr->high, next->low-1) : rr->high;
            }
      }

      if (!range_found)
      {
            Range r = *rr;
            g_array_insert_val (a, low_index, r);
            --low_index;
            ++high_index;
      }

      for (i=low_index; i<=high_index && i<a->len; )
      {
            if (maybe_merge_ranges (a, i))
                  --high_index;
            else
                  ++i;
      }

      return retval;
}

static gulong
mark_range_unread (Newsrc * n, const Range * ur)
{
      int i;
      int low_index = -1;
      int high_index = -1;
      gboolean contains_low = FALSE;
      gboolean contains_high = FALSE;
      gulong retval = 0;
      GArray * a = n->read;

      low_index = lower_bound (&ur->low,
                               a->data,
                               a->len,
                               sizeof(Range),
                               compare_pulong_to_pRange,
                               &contains_low);

      high_index = low_index + lower_bound (&ur->high,
                                            (Range*)a->data+low_index,
                                            a->len-low_index,
                                            sizeof(Range),
                                            compare_pulong_to_pRange,
                                            &contains_high);

      for (i=low_index; i<=high_index && i<a->len; )
      {
            Range * r = &g_array_index (a, Range, i);
            if (range_contains (ur, r)) /* remove */
            {
                  retval += (r->high+1) - r->low;
                  g_array_remove_index (a, i);
                  --high_index;
            }
            else if (r->low!=ur->low && r->high!=ur->high && range_contains (r, ur)) /* split */
            {
                  Range range;
                  range.high = r->high;
                  r->high = ur->low-1;
                  range.low = ur->high+1;
                  retval += ur->high+1-ur->low;
                  g_array_insert_val (a, low_index+1, range);
                  ++high_index;
                  i += 2;
            }
            else if (range_contains_point (r, ur->low)) /* change high */
            {
                  retval += r->high+1 - ur->low;
                  r->high = ur->low-1;
                  ++i;
            }
            else if (range_contains_point (r, ur->high)) /* change low */
            {
                  retval += ur->high+1 - r->low;
                  r->low = ur->high+1;
                  ++i;
            }
            else ++i;
      }

      return retval;
}

gulong
newsrc_mark_range (Newsrc * n, gulong low, gulong high, gboolean read)
{
      Range r;

      g_return_val_if_fail (n!=NULL, 0);

      if (n->read == NULL)
            init_read (n);

      r.low = MIN(low, high);
      r.high = MAX(low, high);

      return read
            ? mark_range_read (n, &r)
            : mark_range_unread (n, &r);
}

gboolean
newsrc_mark_article (Newsrc * n, gulong number, gboolean read)
{
      gulong changed_qty;

      g_return_val_if_fail (n!=NULL, FALSE);

      changed_qty = newsrc_mark_range (n, number, number, read);

      if (read)
            return changed_qty==0;
      else /* unread */
            return changed_qty!=0;
}

/************
*************  PROTECTED
************/

static void
newsrc_destructor (PanObject * obj)
{
      Newsrc * n = NEWSRC (obj);

      debug1 (DEBUG_PAN_OBJECT, "newsrc_dtor: %p", n);
      g_free (n->read_str);
      if (n->read != NULL)
            g_array_free (n->read, TRUE);

      pan_object_destructor (obj);
}

static void
newsrc_constructor (Newsrc * n,
                    const char * read_str,
                    gulong low,
                    gulong high)
{
      /* construct parent */
      pan_object_constructor (PAN_OBJECT(n), newsrc_destructor);

      /* construct this class' bits */
      debug1 (DEBUG_PAN_OBJECT, "newsrc_ctor: %p", n);
      n->group_low = 0;
      n->group_high = 0;
      n->read = NULL;
      n->read_str = NULL;

      newsrc_init (n, read_str, low, high);
}


/************
*************  PUBLIC
************/

void
newsrc_init_from_range_str (Newsrc      * n,
                            const char  * range_str)
{
      gulong low, high;
      GArray * a;

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

      /* article numbers */
      replace_gstr (&n->read_str, g_strdup(range_str));
      if (is_nonempty_string (n->read_str))
            g_strstrip (n->read_str);

      /* remove the old ranges & add the new */
      if (n->read != NULL)
            g_array_free (n->read, TRUE);
      a = n->read = g_array_new (FALSE, FALSE, sizeof(Range));
      parse_read_str (n, n->read_str, n->read);

      /* update the low/high based on the imported ranges */
      low = high = 0;
      if (a->len != 0) {
            low = g_array_index(a,Range,0).low;
            high = g_array_index (a, Range, a->len-1).high;
      }
      newsrc_set_group_range (n, low, high);
}


void
newsrc_init (Newsrc * n, const char * read_str, gulong low, gulong high)
{
      g_return_if_fail (n!=NULL);

      newsrc_init_from_range_str (n, read_str);
      newsrc_set_group_range (n, low, high);
}

Newsrc*
newsrc_new (const char * read_str, gulong low, gulong high)
{
      Newsrc * e = g_new (Newsrc, 1);
      newsrc_constructor (e, read_str, low, high);
      return e;
}

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

void
newsrc_get_group_range  (Newsrc             * newsrc,
                         gulong             * group_low,
                         gulong             * group_high)
{
      g_return_if_fail (newsrc!=NULL);

      if (group_low != NULL)
            *group_low = newsrc->group_low;
      if (group_high != NULL)
            *group_high = newsrc->group_high;
}

void
newsrc_set_group_range (Newsrc * n, gulong low, gulong high)
{
      gboolean exact_match;
      int low_index;
      int high_index;

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

      n->group_low = low;
      n->group_high = high;

      /* crop n->read */
      if (n->read == NULL)
            init_read (n);

      exact_match = FALSE;
      low_index = lower_bound (&low,
                             n->read->data,
                             n->read->len,
                             sizeof(Range),
                             compare_pulong_to_pRange,
                             &exact_match);
      if (exact_match) {
            Range * r = &g_array_index (n->read,Range,low_index);
            r->low = low;
      }

      exact_match = FALSE;
      high_index = lower_bound (&high,
                              n->read->data,
                              n->read->len,
                              sizeof(Range),
                              compare_pulong_to_pRange,
                              &exact_match);
      if (exact_match) {
            Range * r = &g_array_index (n->read,Range,high_index);
            r->high = high;
      } else
            --high_index;

      if (low_index <= high_index) /*keep the items in the range */
      {
            GArray * tmp = g_array_new (FALSE, FALSE, sizeof(Range));
            g_array_append_vals (tmp, &g_array_index(n->read,Range,low_index), high_index+1-low_index);
            g_array_free (n->read, TRUE);
            n->read = tmp;
      }
      else /* nothing left */
      {
            g_array_set_size (n->read, 0);
      }
}

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

void
newsrc_mark_all (Newsrc * n, gboolean read)
{
      /* sanity clause */
      g_return_if_fail (n!=NULL);

      if (n->read == NULL)
            init_read (n);

      /* clear the ranges, effectively marking all unread... */
      g_array_set_size (n->read, 0);

      if (read)
      {
            /* create an all-inclusive range, marking all read... */
            Range range;
            range.low = 0;
            range.high = n->group_high;
            g_array_append_val (n->read, range);
      }
}

gboolean
newsrc_is_article_read (const Newsrc * n, gulong article_number)
{
      gboolean exact_match = FALSE;

      /* sanity clause */
      g_return_val_if_fail (n!=NULL, FALSE);
      g_return_val_if_fail (article_number!=0, FALSE);

      if (n->read == NULL)
            init_read ((Newsrc*)n);

      lower_bound (&article_number,
                   n->read->data,
                   n->read->len,
                   sizeof(Range),
                   compare_pulong_to_pRange,
                   &exact_match);

      return exact_match;
}

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

gboolean
newsrc_parse_raw_line (const char     * raw_line,
                       GString        * setme_group_name,
                       gboolean       * setme_subscribed,
                       GString        * setme_range_string)
{
      const char * pch = raw_line;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(raw_line), FALSE);

      /* subscription */
      while (*pch && !isspace((guchar)*pch)) ++pch;
      --pch;
      if (setme_subscribed != NULL)
            *setme_subscribed = *pch==':';

      /* group name */
      if (setme_group_name != NULL) {
            g_string_truncate (setme_group_name, 0);
            g_string_append_len (setme_group_name, raw_line, pch-raw_line);
            pan_g_string_strstrip (setme_group_name);
      }
      
      /* article numbers */
      ++pch;
      while (*pch==' ') ++pch;
      if (setme_range_string != NULL) {
            g_string_assign (setme_range_string, pch);
            pan_g_string_strstrip (setme_range_string);
      }

      return TRUE;
}

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

char*
newsrc_get_read_str (const Newsrc * n)
{
      char * retval = NULL;

      if (n!=NULL && n->read!=NULL) /* someone's been changing the read/unread */
      {
            int i;
            const int max_chars_per_range = 22; /* "%lu-%lu," w/10 digits for %lu */
            GString * str = g_string_sized_new (max_chars_per_range * n->read->len);

            for (i=0; i<n->read->len; ++i)
            {
                  const Range * r = &g_array_index (n->read, Range, i);

                  /* trim to group range */
                  if (r->high < n->group_low) continue;
                  if (r->low > n->group_high) continue;

                  g_string_append_printf (str, "%lu", r->low);

                  if (r->low!=r->high)
                        g_string_append_printf (str, "-%lu", r->high);

                  if (i != n->read->len-1)
                        g_string_append_c (str, ',');
            }

            retval = g_string_free (str, FALSE);
      }
      else if (n!=NULL && is_nonempty_string(n->read_str)) /* just use the fields passed in */
      {
            retval = g_strdup (n->read_str);
      }
      else
      {
            retval = g_strdup ("");
      }

      return retval;
}

char*
newsrc_export_line (const Newsrc  * n,
                    const char    * group_name,
                    gboolean        subscribed)
{
      char * buf;
      GString * str;

      g_return_val_if_fail (is_nonempty_string(group_name), NULL);

      str = g_string_sized_new (1024);
      g_string_append (str, group_name);
      g_string_append_c (str, subscribed ? ':' : '!');
      g_string_append_c (str, ' ');

      buf = NULL;
      if (n != NULL)
            buf = newsrc_get_read_str (n);
      if (buf != NULL) {
            g_string_append (str, buf);
            g_free (buf);
      }

      return g_string_free (str, FALSE);
}

Generated by  Doxygen 1.6.0   Back to index