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

task-grouplist.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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <glib.h>

#include <pan/base/article.h>
#include <pan/base/debug.h>
#include <pan/base/log.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>

#include <pan/nntp.h>
#include <pan/task-grouplist.h>

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

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

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

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

static void task_grouplist_run (Task*, PanSocket*);

static TaskStateEnum nntp_grouplist_download_all (TaskGroupList*, PanSocket*);

static int nntp_grouplist_download_new (TaskGroupList*, PanSocket*);

static gchar* task_grouplist_describe (const StatusItem*);

static void task_grouplist_destructor (PanObject*);

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

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

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

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

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

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

PanObject*
task_grouplist_new (Server *server,
                    GrouplistDownloadType dl_type)
{
      TaskGroupList *item = g_new0 (TaskGroupList, 1);

      debug1 (DEBUG_PAN_OBJECT, "task_grouplist_new: %p", item);
      
      /* construct superclass... */
      task_constructor (TASK(item), task_grouplist_destructor, task_grouplist_describe, server, TRUE);
      task_state_set_work_need_socket (&TASK(item)->state, server, task_grouplist_run);

      /* construct this class... */
      item->server = server;
      item->download_type = dl_type;
      item->got_list = FALSE;
      item->groups = g_ptr_array_new ();

      return PAN_OBJECT(item);
}

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

static void
task_grouplist_destructor (PanObject * o)
{
      TaskGroupList * task = TASK_GROUPLIST(o);

      /* destruct this class */
      debug1 (DEBUG_PAN_OBJECT, "task_grouplist_destructor: %p", o);
      g_ptr_array_free (task->groups, TRUE);

      /* destruct the superclass */
      task_destructor (o);
}

static char*
task_grouplist_describe (const StatusItem* item)
{
      int type;
      char * retval;
      const char * name;

      /* sanity checks */
      g_return_val_if_fail (item!=NULL, NULL);
      type = TASK_GROUPLIST(item)->download_type;
      name = server_get_name (TASK_GROUPLIST(item)->server);

      if (type == GROUPLIST_ALL)
            retval = g_strdup_printf (_("Getting all groups from server \"%s\""), name);
      else if (type == GROUPLIST_NEW)
            retval = g_strdup_printf (_("Getting new groups from server \"%s\""), name);
      else
            retval = g_strdup ("BUG!");

      return retval;
}

static void
task_grouplist_run (Task * task, PanSocket * sock)
{
      TaskGroupList * task_g = TASK_GROUPLIST(task);
      Server * server = task->server;
      TaskStateEnum val;

      status_item_emit_status_va (STATUS_ITEM(task),
                                  _("Got %d groups from server \"%s\""),
                                  (int)task_g->groups->len,
                                  server_get_name (server));

      /**
      ***  Try to get the groups from the server
      **/

      val = TASK_OK;
      switch (task_g->download_type)
      {
            case GROUPLIST_ALL:
                  val = nntp_grouplist_download_all (task_g, sock);
                  break;
            case GROUPLIST_NEW:
                  val = nntp_grouplist_download_new (task_g, sock);
                  break;
      }

      /**
      ***  Add any that we got
      **/

      task_state_set_health (&task->state, val);

      if (val != TASK_OK)
      {
            task_state_set_work_need_socket (&task->state, task->server, task_grouplist_run);
      }
      else
      {
            /* for the next time we call GROUPLIST_NEW... */
            server->last_newgroup_list_time = time(0);

            /* let the user know what's going on ... */
            status_item_emit_status_va (STATUS_ITEM(task),
                                  _("Adding groups to server \"%s\""),
                                  server_get_name (server));

            /* add the groups we don't already have */
            if (task_g->groups->len > 0) {
                  server_add_groups (server, (Group**)task_g->groups->pdata, task_g->groups->len, NULL, NULL);
                  if (task_g->download_type == GROUPLIST_ALL)
                        server_remove_unused_groups (server, (Group **)task_g->groups->pdata, task_g->groups->len);
            }

            g_ptr_array_set_size (task_g->groups, 0);

            task_state_set_work_completed (&task->state);
      }
}


/*---[ nntp_grouplist_download ]--------------------------------------
 *
 *--------------------------------------------------------------------*/

static int
compare_name_ppgroup (const void * a, const void * b)
{
      return pstring_compare ((const PString*)a, &(*(const Group**)b)->name);
}

static int
compare_ppgroup_ppgroup (gconstpointer a, gconstpointer b, gpointer unused)
{
      return pstring_compare (&(*(const Group**)a)->name,
                              &(*(const Group**)b)->name);
}


static TaskStateEnum
nntp_grouplist_download_all (TaskGroupList * item, PanSocket * sock)
{
      GPtrArray * groups = item->groups;
      const gboolean * const abort = &TASK(item)->hint_abort;
      Server * server = TASK(item)->server;
      int description_count = 0;
      gint val = 0;
      const char * response;
      int response_number;
      StatusItem * status = STATUS_ITEM(item);

      status_item_emit_status_va (status,
            _("Got %d groups from server \"%s\""),
            (int)groups->len,
            server_get_name (server));

      /**
      ***  Get the group list, if we haven't already
      **/

      if (!item->got_list)
      {
            TaskStateEnum val;
            GString * name;
            GString * permission;

            /* send the list command */
            val = *abort ? TASK_FAIL : nntp_command (status, sock, &response, &response_number, "LIST");
            if (val != TASK_OK)
                  return val;
            if (response_number != 215) {
                  status_item_emit_error_va (status, ("Group LIST command failed: %s"), response);
                  return TASK_FAIL;
            }

            /* read the group list from the server. */
            name = g_string_new (NULL);
            permission = g_string_new (NULL);
            for (;;)
            {
                  const char * march;
                  Group * group;

                  /* get the next line */ 
                  val = *abort ? TASK_FAIL : pan_socket_getline (sock, &response);
                  if (val != TASK_OK)
                        break;

                  /* check for end of list */
                  if (!strcmp(response,".\r\n"))
                        break;

                  /* parse the data line */
                  march = response;
                  get_next_token_g_str (march, ' ', &march, name);
                  skip_next_token (march, ' ', &march); /* skip low number */
                  skip_next_token (march, ' ', &march); /* skip high number */
                  get_next_token_g_str (march, ' ', &march, permission);

                  /* create a new group object */
                  group = group_new (server, name->str);
                  group->permission = *permission->str;
                  g_ptr_array_add (groups, group);

                  /* periodic status feedback */
                  if (groups->len % 256 == 0)
                        status_item_emit_status_va (status,
                              _("Got %d groups from server \"%s\""),
                              (int)groups->len,
                              server_get_name (server));
            }
            g_string_free (permission, TRUE);
            g_string_free (name, TRUE);

            /* make sure the new groups get saved */
            if (groups->len)
                  server_set_group_type_dirty (server, SERVER_GROUPS_ALL);

            item->got_list = val == TASK_OK;

            if (val != TASK_OK)
                  return val;
      }

      /* sort so that we can bsearch them... */
      g_ptr_array_sort_with_data (groups, compare_ppgroup_ppgroup, NULL);


      /**
      ***  DESCRIPTIONS
      **/

      /* now try to get the descriptions...
         this is implemented as a second pass because the overlap between
         LIST and LIST NEWSGROUPS isn't well defined:  The latter may only
         show the groups with a description, and may not be available
         on older servers.  To make sure we get all the groups, and
         descriptions for as many as we can, we use both commands.
         -- csk */

      val = *abort ? TASK_FAIL : nntp_command (status, sock, &response, &response_number, "LIST NEWSGROUPS");
      if (val != TASK_OK)
            return val;
      if (response_number != 215) {
            /* no big deal; possibly server doesn't support this command */
            status_item_emit_error_va (status, _("List Newsgroups failed: %s"), response);
            return TASK_OK;
      }

      /* march through the descriptions */
      description_count = 0;
      for (;;)
      {
            const char * pch;
            char * name = NULL;
            char * description = NULL;

            /* read the next line */
            val = *abort ? TASK_FAIL : pan_socket_getline (sock, &response);
            if (val != TASK_OK)
                  break;

            /* check for end of line */
            if (!strcmp(response,".\r\n"))
                  break;      

            /* get the name */
            pch = response;
            while (*pch && !isspace((int)*pch)) ++pch;
            name = g_strndup (response, pch-response);
            response = pch;

            /* get the description */
            if ((pch = pan_strstr (response, "\r\n"))) {
                  description = g_strndup (response, pch-response);
                  g_strstrip (description);
            }

            /* let the user know we're still on the job */
            ++description_count;
            if (description_count % 43 == 0)
                  status_item_emit_status_va (STATUS_ITEM(item),
                                              _("Got %d descriptions from server \"%s\""),
                                              description_count,
                                              server_get_name (server));

            /* try to update the group's description field */
            if (is_nonempty_string(name) &&
                is_nonempty_string(description))
            {
                  int i;
                  gboolean exact_match = FALSE;
                  const PString name_pstring = pstring_shallow (name, -1);

                  i = lower_bound (&name_pstring,
                                   groups->pdata, groups->len,
                               sizeof(gpointer),
                               compare_name_ppgroup,
                               &exact_match);
                  if (exact_match)
                  {
                        Group * g = GROUP(g_ptr_array_index(groups,i));
                        replace_gstr (&g->description, description);
                        description = NULL;
                  }
            }

            g_free (name);
            g_free (description);
      }

      if (val == TASK_OK)
            log_add_va (LOG_INFO, _("Got %d groups from server \"%s\""),
                  groups->len,
                  server_get_name (server));

      return val;
}


/*--------------------------------------------------------------------
 * get "new" groups
 * 
 * based on RFC977 - JEL
 *--------------------------------------------------------------------*/
static TaskStateEnum
nntp_grouplist_download_new (TaskGroupList * item, PanSocket * sock)
{
      GString * name;
      GPtrArray * groups = item->groups;
      const gboolean * const abort = &TASK(item)->hint_abort;
      Server *server = TASK(item)->server;
      const char * response;
      int response_number = 0;
      TaskStateEnum val;
      gint count = 0;
      char datestr[64];
      struct tm gmt;
      StatusItem * status = STATUS_ITEM(item);
      Group * group = NULL;

      status_item_emit_status_va (STATUS_ITEM(item),
            _("Getting %d new groups from server \"%s\""),
            0, server_get_name (server));

      /* ask the server which groups have been added since then */
      pan_gmtime_r (&server->last_newgroup_list_time, &gmt);
      strftime (datestr, sizeof(datestr), "%Y%m%d %H%M%S GMT", &gmt);
      val = *abort ? TASK_FAIL : nntp_command_va (status, sock, &response, &response_number, "NEWGROUPS %s", datestr);
      if (val != TASK_OK)
            return val;

      /* 501 Usage: NEWGROUPS yymmdd hhmmss ["GMT"] */
      if (response_number == 501)
      {
            /* Paul McGarrry <mcgarray@tig.com.au> and his Newsmaster
             * Simon Lyall <simon.lyall@ihug.co.nz> report that nntpcache
             * isn't handling four-digit years.  So if the four-digit year
             * fails, let's fall back to a two-digit year.
             */
            val = *abort ? TASK_FAIL : nntp_command_va (status, sock, &response, &response_number, "NEWGROUPS %s", datestr+2);
            if (val != TASK_OK)
                  return val;
      }

      /* 231 = list of new groups follows */
      if (response_number != 231)
      {
            status_item_emit_error_va (status, _("New groups retrieval failed: %s"), response);
            return TASK_FAIL;
      }

      /**
      ***  Get the list of new groups
      **/

      name = g_string_new (NULL);
      val = *abort ? TASK_FAIL : pan_socket_getline (sock, &response);
      while (val==TASK_OK && strncmp(response, ".\r\n", 3))
      {
            /* create a Group */
            get_next_token_g_str (response, ' ', NULL, name);
            group = group_new (server, name->str);
            group_set_dirty (group); /* make sure it gets saved */
            group->flags |= GROUP_NEW;
            g_ptr_array_add (groups, group);

            if (++count % 43 == 0)
            {
                  status_item_emit_progress (STATUS_ITEM(item), count);

                  status_item_emit_status_va (STATUS_ITEM(item),
                        _("Getting %d new groups from server \"%s\""), 
                        count, server_get_name (server));
            }

            val = *abort ? TASK_FAIL : pan_socket_getline (sock, &response);
      }

      g_string_free (name, TRUE);

      if (val == TASK_OK)
      {
            char buf[512];
            g_snprintf (buf, sizeof(buf), _("Got %d groups from server \"%s\""), count, server_get_name(server));
            log_add (LOG_INFO, buf);
            status_item_emit_status (STATUS_ITEM(item), buf);
      }

      return val;
}

Generated by  Doxygen 1.6.0   Back to index