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

server.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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <glib.h>

#include <pan/base/debug.h>
#include <pan/base/file-grouplist.h>
#include <pan/base/file-headers.h>
#include <pan/base/log.h>
#include <pan/base/newsrc-port.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/server.h>
#include <pan/base/serverlist.h>

const PString INTERNAL_SERVER_NAME =  { "folders", 7, 0u };

static void fire_groups_added          (Server*, Group**, gint group_qty);
static void fire_groups_removed        (Server*, Group**, gint group_qty);

/* this option for turning off saving exists for the regression tests,
 * which need to be able to turn it off */
int server_saving_enabled = TRUE;

/**
***  Server Object Life Cycle
**/

void
server_constructor (Server              * server,
                    PanObjectDestructor   destructor)
{
      PanObject *obj = PAN_OBJECT(server);

      /* init the superclass bits */
      pan_object_constructor (obj, destructor);

      /* init the status-item bits */
      debug1 (DEBUG_PAN_OBJECT, "server_constructor: %p", server);
      server->posting = FALSE;
      server->need_auth = FALSE;
      server->use_newsrc = FALSE;
      server->newsrc_imported = FALSE;
      server->address = PSTRING_INIT;
      server->name = PSTRING_INIT;
      server->username = PSTRING_INIT;
      server->password = PSTRING_INIT;
      server->newsrc_filename = PSTRING_INIT;
      server->port = 25;
      server->max_connections = 2;
      server->idle_secs_before_timeout = 180;
      server->last_newgroup_list_time = 1; /* Typhoon can't handle 0 */
      server->_groups_dirty = 0;
      server->_groups_loaded = 0;
      server->_groups = g_ptr_array_new ();
      server->_group_memchunk = memchunk_new (sizeof(Group), 1024, FALSE);
}

void
server_destructor (PanObject *obj)
{
      guint i;
      Server * server = SERVER(obj);

      /* export the .newsrc file if necessary */
      if (server->use_newsrc && pstring_is_set (&server->newsrc_filename))
            newsrc_export (server, &server->newsrc_filename, FALSE);

      /* clear out the server bits: groups */
      server_save_if_dirty (server, NULL);
      for (i=0; i!=server->_groups->len; ++i)
            group_destructor (GROUP(g_ptr_array_index(server->_groups,i)));
      g_ptr_array_free (server->_groups, TRUE);
      memchunk_destroy (server->_group_memchunk);
      server->_groups = NULL;
      server->_group_memchunk = NULL;

      /* clear out the server bits: strings */
      pstring_clear (&server->name);
      pstring_clear (&server->address);
      pstring_clear (&server->username);
      pstring_clear (&server->password);
      pstring_clear (&server->newsrc_filename);

      /* clear out the parent */
      pan_object_destructor (obj);
}

Server*
server_new (void)
{
      Server* server = g_new0 (Server, 1);
      debug1 (DEBUG_PAN_OBJECT, "server_new: %p", server);
      server_constructor (server, server_destructor);
      return server;
}

/***
****
****  GROUP LOADING
****
***/

ServerGroupsType
server_get_group_type (const Group * group)
{
      ServerGroupsType type;

      /* sanity clause */
      g_return_val_if_fail (group!=NULL, 0);

      /* or the group types together */
            type = 0;
      if (group_is_subscribed (group))
            type |= SERVER_GROUPS_SUBSCRIBED;
      else
            type |= SERVER_GROUPS_UNSUBSCRIBED;

      return type;
}

static ServerGroupsType
server_get_groups_type (const Group ** groups, gint qty)
{
      gint i;
      ServerGroupsType type = 0;

      /* sanity clause */
      g_return_val_if_fail (groups!=NULL, 0);
      g_return_val_if_fail (qty>0, 0);

      /* or the groups' types together */
      for (i=0; i<qty; ++i)
            type |= server_get_group_type (groups[i]);

      return type;
}



void
server_ensure_groups_loaded (Server * server, ServerGroupsType type)
{
      gboolean load_needed;
      debug_enter ("server_ensure_groups_loaded");

      /* sanity clause */
      g_return_if_fail (server_is_valid(server));
      g_return_if_fail (type!=0);

      /* if we're going to load groups, start up a statusitem */
      load_needed = (type|server->_groups_loaded) != server->_groups_loaded;
      if (load_needed)
      {
            ServerGroupsType dirty;
            ServerGroupsType types_to_load;
            
            /* only load the ones we haven't already loaded */
            types_to_load = type & ~server->_groups_loaded;
            server->_groups_loaded |= type;

            /* load whatever needs loading */
            dirty = server->_groups_dirty;
            file_grouplist_load (server, types_to_load, NULL);
            server->_groups_dirty = dirty;
      }

      debug_exit ("server_ensure_groups_loaded");
}

GPtrArray*
server_get_groups (Server * server, ServerGroupsType type)
{
      int i;
      GPtrArray * a = g_ptr_array_new ();
      debug_enter ("server_get_groups");

      /* sanity clause */
      g_return_val_if_fail (server_is_valid(server), a);
      g_return_val_if_fail (type!=0, a);

      /* make sure all the needed groups are loaded */
      server_ensure_groups_loaded (server, type);

      /* make an array of all the requested groups */
      pan_g_ptr_array_reserve (a, server->_groups->len); 
      for (i=0; i<server->_groups->len; ++i) {
            gboolean use = FALSE;
            Group * g = GROUP(g_ptr_array_index(server->_groups,i));
            if (!use && (type & SERVER_GROUPS_SUBSCRIBED))
                  use = group_is_subscribed (g);
            if (!use && (type & SERVER_GROUPS_UNSUBSCRIBED))
                  use = !group_is_subscribed (g);
            if (use)
                  g_ptr_array_add (a, g);
      }

      debug_exit ("server_get_groups");
      return a;
}

void
server_set_group_type_dirty (Server * server, ServerGroupsType type)
{
      debug_enter ("server_set_group_type_dirty");

      /* sanity clause */
      g_return_if_fail (server_is_valid(server));
      g_return_if_fail (type!=0);

      /* mark dirty */
      server_ensure_groups_loaded (server, type);
      server->_groups_dirty |= type;

      debug_exit ("server_set_group_type_dirty");
}

void
server_save_grouplist_if_dirty (Server * server, StatusItem * status)
{
      GStaticMutex mutex = G_STATIC_MUTEX_INIT;

      /* sanity clause */
      g_return_if_fail (server_is_valid(server));
      g_return_if_fail (server_saving_enabled);

      /* save anything that's dirty */
      g_static_mutex_lock (&mutex);
      if (server->_groups_dirty)
      {
            file_grouplist_save (server, server->_groups_dirty, status);
            server->_groups_dirty = FALSE;
      }
      g_static_mutex_unlock (&mutex);
}

void
server_save_if_dirty (Server * server, StatusItem * status)
{
      guint i;
      debug_enter ("server_save_if_dirty");

      g_return_if_fail (server_is_valid(server));

      server_save_grouplist_if_dirty (server, status);

      for (i=0; i<server->_groups->len; ++i)
      {
            Group * group = GROUP(g_ptr_array_index(server->_groups,i));
            if (group->articles_dirty)
            {
                  group_ref_articles (group, NULL);
                  file_headers_save (group, status);
                  group_unref_articles (group, NULL);
            }
      }

      debug_exit ("server_save_if_dirty");
}

/***
****
****  GROUPLIST
****
***/

struct _Group*
server_alloc_new_group (Server * server)
{
      Group * group;

        g_return_val_if_fail (server_is_valid (server), NULL);

      group = (Group*) memchunk_alloc (server->_group_memchunk);
      group->server = server;

      return group;
}

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

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

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


static void
server_add_groups_impl (Server       * server,
                        Group       ** groups,
                        gint           group_qty,
                        GPtrArray    * fillme_used,
                        GPtrArray    * fillme_not_used,
                        gboolean       initializing)
{
      int i;
      GHashTable * old;
      GPtrArray * added;
      debug_enter ("server_add_groups_impl");

      /* entry assertions */
      g_return_if_fail (server_is_valid (server));
      g_return_if_fail (groups != NULL);
      g_return_if_fail (group_qty >= 1);
      for (i=0; i<group_qty; ++i) {
            g_return_if_fail (groups[i] != NULL);
            g_return_if_fail (groups[i]->server == server);
      }

      /* ensure the groups sets are loaded before adding to them */
      if (1) {
            ServerGroupsType type;
            type = server_get_groups_type ((const Group **)groups, group_qty);
            server_ensure_groups_loaded (server, type);
      }

      /* convert the existing grouplist to a hashtable */
      old = g_hash_table_new (pstring_hash, pstring_equal);
      for (i=0; i<server->_groups->len; ++i) {
            Group * group = GROUP(g_ptr_array_index(server->_groups,i));
            g_hash_table_insert (old, &group->name, group);
      }

      /* try to add the new groups */
      added = g_ptr_array_sized_new (group_qty);
      for (i=0; i<group_qty; ++i)
      {
            Group * new_group = groups[i];
            gboolean has_group = g_hash_table_lookup (old, &new_group->name) != NULL;

            if (!has_group)
            {
                  /* add the group */
                  g_hash_table_insert (old, &new_group->name, new_group);
                  g_ptr_array_add (added, new_group);
            }
            else
            {
                  if (fillme_not_used != NULL)
                        g_ptr_array_add (fillme_not_used, new_group);
            }
      }

      /* if any change was made, update the states */
      if (added->len != 0)
      {
            /* update the groups array */
            pan_hash_to_ptr_array  (old, server->_groups);
            g_ptr_array_sort_with_data (server->_groups, compare_ppgroup_ppgroup_name, NULL);

            if (!initializing)
            {
                  /* mark the appropriate sets as dirty */
                  ServerGroupsType type = server_get_groups_type ((const Group**)added->pdata, added->len);
                  server_set_group_type_dirty (server, type);

                  /* let listeners know which were added */
                  fire_groups_added (server, (Group**)added->pdata, added->len);

                  /* let the caller know which were added */
                  if (fillme_used != NULL)
                        pan_g_ptr_array_assign (fillme_used, added->pdata, added->len);

                  /* save them for safe-keeping */
                  server_save_grouplist_if_dirty (server, NULL);
            }
      }

      /* cleanup */
      g_hash_table_destroy (old);
      g_ptr_array_free (added, TRUE);
      debug_exit ("server_add_groups_impl");
}

void
server_add_groups (Server       * server,
                   Group       ** groups,
                   gint           group_qty,
                   GPtrArray    * fillme_used,
                   GPtrArray    * fillme_not_used)
{
      server_add_groups_impl (server, groups, group_qty, fillme_used, fillme_not_used, FALSE);
}

void
server_init_groups (Server       * server,
                    Group       ** groups,
                    gint           group_qty,
                    GPtrArray    * fillme_used,
                    GPtrArray    * fillme_not_used)
{
      server_add_groups_impl (server, groups, group_qty, fillme_used, fillme_not_used, TRUE);
}

void
server_remove_groups (Server * server, Group ** groups, int qty)
{
      guint i;
      ServerGroupsType type;
      GPtrArray * removed;
      debug_enter ("server_remove_groups");

      /* sanity clause */
      g_return_if_fail (server_is_valid (server));
      g_return_if_fail (qty > 0);
      for (i=0; i<qty; ++i) {
            g_return_if_fail (groups[i] != NULL);
            g_return_if_fail (groups[i]->server == server);
      }

      /* ensure the groupset is loaded */
      type = server_get_groups_type ((const Group **)groups, qty);
      server_ensure_groups_loaded (server, type);

      /* find the groups to remove */
      removed = g_ptr_array_new ();
      for (i=0; i<qty; ++i)
      {
            Group * group = groups[i];
            gboolean exact_match = FALSE;
            gint index;

            /* do we have a match? */
            index = lower_bound (group,
                                   server->_groups->pdata,
                                   server->_groups->len,
                                   sizeof(gpointer),
                                   compare_pgroup_ppgroup_name,
                               &exact_match);

            /* if so, remove it */
            if (exact_match)
                  g_ptr_array_add (removed, g_ptr_array_remove_index (server->_groups, index));
      }

      /* remove the groups */
      if (removed->len != 0) {
            fire_groups_removed (server, (Group**)removed->pdata, removed->len);
            server_set_group_type_dirty (server, type);
      }

      /* cleanup */
      g_ptr_array_free (removed, TRUE);
      debug_exit ("server_remove_groups");
}

void
server_remove_unused_groups (Server * server, Group ** new_groups, gint qty)
{
      guint i;
      ServerGroupsType type;
      GHashTable * old = NULL;
      debug_enter ("server_remove_unused_groups");

      /* sanity clause */
      g_return_if_fail (server_is_valid (server));
      g_return_if_fail (qty > 0);
      for (i=0; i<qty; ++i) {
            g_return_if_fail (new_groups[i] != NULL);
            g_return_if_fail (new_groups[i]->server == server);
      }

      /* ensure the groupset is loaded */
      type = server_get_groups_type ((const Group **)new_groups, qty);
      server_ensure_groups_loaded (server, type);

      /* convert the existing grouplist to a hashtable */
      old = g_hash_table_new (pstring_hash, pstring_equal);
      for (i=0; i<server->_groups->len; ++i) {
            Group * group = GROUP(g_ptr_array_index(server->_groups,i));
            g_hash_table_insert (old, &group->name, group);
      }

      /* remove all new groups */
      for (i=0; i<qty; i++)
            g_hash_table_remove (old, &new_groups[i]->name);

      /* if any groups left, remove them */
      if (g_hash_table_size (old) > 0) {
            GPtrArray * old_groups = g_ptr_array_new ();
            pan_hash_to_ptr_array (old, old_groups);
            server_remove_groups (server, (Group **)old_groups->pdata, old_groups->len);
            g_ptr_array_free (old_groups, TRUE);
      }

      /* cleanup */
      g_hash_table_destroy (old);
}

Group*
server_get_named_group_in_type (Server            * server,
                                const PString     * name,
                                ServerGroupsType    set)
{
      int index;
      Group * retval = NULL;
      gboolean exact_match = FALSE;
      debug_enter ("server_get_named_group_in_type");

      /* sanity clause */
      g_return_val_if_fail (server_is_valid (server), NULL);
      g_return_val_if_fail (pstring_is_set (name), NULL);
      g_return_val_if_fail (set!=0, NULL);

      /* find where to add this group */
      index = lower_bound (name,
                       server->_groups->pdata,
                       server->_groups->len,
                       sizeof(gpointer),
                       compare_name_ppgroup_name,
                       &exact_match);
      if (!exact_match)
      {
            /* maybe the group's not loaded yet */
            server_ensure_groups_loaded (server, set);
            index = lower_bound (name,
                             server->_groups->pdata,
                             server->_groups->len,
                             sizeof(gpointer),
                             compare_name_ppgroup_name,
                             &exact_match);
      }

      if (exact_match)
            retval = GROUP(g_ptr_array_index(server->_groups,index));

      debug_exit ("server_get_named_group_in_type");
      return retval;
}

Group*
server_get_named_group (Server        * server,
                      const PString * name)
{
      return server_get_named_group_in_type (server, name, SERVER_GROUPS_ALL);
}

gboolean
server_is_valid (const Server * server)
{
      g_return_val_if_fail (server!=NULL, FALSE);
      g_return_val_if_fail (pstring_is_valid (&server->name), FALSE);
      g_return_val_if_fail (server->_group_memchunk!=NULL, FALSE);
      g_return_val_if_fail (server->_groups!=NULL, FALSE);
      /* arbitrary fishing for corruption */
      g_return_val_if_fail ((server->_groups_dirty & ~SERVER_GROUPS_ALL) == 0, FALSE);
      g_return_val_if_fail ((server->_groups_loaded & ~SERVER_GROUPS_ALL) == 0, FALSE);

      return TRUE;
}

const char*
server_get_name (const Server * server)
{
      g_return_val_if_fail (server_is_valid (server), "");

      return server->name.str;
}

void
server_set_name (Server          * server,
                 const PString   * new_name,
                 GError         ** error)
{
      gboolean ok = TRUE;
      PString my_new_name;

      /* sanity clause */
      g_return_if_fail (server != NULL);
      g_return_if_fail (pstring_is_valid (new_name));
      g_return_if_fail (error==NULL || *error==NULL);

      /* create a stripped copy of new_name */
      my_new_name = pstring_strstrip_shallow (new_name);

      /* make sure a name's specified */
      if (!pstring_is_set (&my_new_name)) {
            if (error != NULL)
                  *error = g_error_new (g_quark_from_string("server"), 1, _("No name specified!"));
            ok = FALSE;
      }

      if (ok)
      {
            /* if name is changing -- not just being initialized -- tell file_headers */
            if (pstring_is_set(&server->name) && !pstring_equal (&server->name, &my_new_name)) {
                  file_headers_server_name_changed (&server->name, &my_new_name);
                  file_grouplist_server_name_changed (&server->name, &my_new_name);
            }

            /* update the server's name */
            pstring_copy (&server->name, &my_new_name);
      }
}

void
server_destroy_groups (Server * server, struct _Group ** groups, gint qty)
{
      gint i;

      /* sanity clause */
      g_return_if_fail (server != NULL);
      g_return_if_fail (qty > 0);
      for (i=0; i<qty; ++i) {
            g_return_if_fail (groups[i] != NULL);
            g_return_if_fail (groups[i]->server == server);
      }

      /* destroy the data files */
      for (i=0; i<qty; ++i)
            file_headers_destroy (groups[i]);

      /* remove the group from the server list */
      server_remove_groups (server, groups, qty);
}


/***
****
****  EVENTS
****
***/

/**
***  Groups Removed
**/

PanCallback*
server_get_groups_removed_callback (void)
{
      static PanCallback * cb = NULL;
      if (cb==NULL) cb = pan_callback_new ();
      return cb;
}

static void
fire_groups_removed (Server * server, Group ** groups, gint group_qty)
{
      GPtrArray * a;

      g_return_if_fail (server_is_valid(server));
      g_return_if_fail (groups!=NULL);
      g_return_if_fail (group_qty>0);

      a = g_ptr_array_new ();
      pan_g_ptr_array_assign (a, (gpointer*)groups, group_qty);
      pan_callback_call (server_get_groups_removed_callback(), server, a);
      g_ptr_array_free (a, TRUE);
}

/**
***  Groups Removed
**/

PanCallback*
server_get_groups_added_callback (void)
{
      static PanCallback * cb = NULL;
      if (cb==NULL) cb = pan_callback_new ();
      return cb;
}

static void
fire_groups_added (Server * server, Group ** groups, gint group_qty)
{
      GPtrArray * a;

      g_return_if_fail (server_is_valid(server));
      g_return_if_fail (groups!=NULL);
      g_return_if_fail (group_qty>0);

      a = g_ptr_array_new ();
      pan_g_ptr_array_assign (a, (gpointer*)groups, group_qty);
      pan_callback_call (server_get_groups_added_callback(), server, a);
      g_ptr_array_free (a, TRUE);
}

Generated by  Doxygen 1.6.0   Back to index