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

acache.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 <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include <gmime/gmime.h>
#include <gmime/memchunk.h>

#include <pan/base/acache.h>
#include <pan/base/argset.h>
#include <pan/base/base-prefs.h>
#include <pan/base/debug.h>
#include <pan/base/message-identifier.h>
#include <pan/base/serverlist.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>
#include <pan/base/util-file.h>
#include <pan/base/util-mime.h>
#include <pan/base/log.h>

static GStaticMutex entries_mutex = G_STATIC_MUTEX_INIT;
static MemChunk * acache_entry_chunk = NULL;
static GHashTable * key_to_messageids = NULL;

static GHashTable * key_to_path = NULL;
static void acache_add_path (const char * key, const char * path);

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

/**
 * This represents one directory (folder) in the cache
 */
00063 typedef struct
{
      char * key;
      char * path;
      gssize size;
}
AcachePath;

/**
 * This represents one file (message) in the cache
 */
00074 typedef struct
{
      AcachePath * path;
      int refcount;
      PString * message_id;
      size_t size;
      time_t date;
}
AcacheEntry;

/**
 * This is the name of the default directory
 */
const char * ACACHE_DEFAULT_KEY = "cache";

#define ACACHE_ENTRY_CHUNK_SIZE 256

/***
****
****  EVENT NOTIFICATION
****
***/

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

static void
fire_bodies_added (const PString ** mids, int qty)
{
      pan_callback_call (acache_get_bodies_added_callback(), mids, GINT_TO_POINTER(qty));
}

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

static void
fire_bodies_removed (const PString ** message_ids, int qty)
{
      pan_callback_call (acache_get_bodies_removed_callback(), message_ids, GINT_TO_POINTER(qty));
}

/***
****
****  DIRECTORIES & FILENAMES
****
***/

static char*
acache_get_message_base (void)
{
      return g_build_filename (get_data_dir(), "messages", NULL);
}
static char*
acache_get_folder_base (void)
{
      char * base = acache_get_message_base ();
      char * retval = g_build_filename (base, "folders", NULL);
      g_free (base);
      return retval;
}

/**
 * Some characters in message-ids don't work well in file names,
 * so we transform them to a safer name.
 */
static char*
acache_message_id_to_basename (char * buf, int len, const PString * message_id)
{
      const char * in;
      char * out;
      debug_enter ("acache_message_id_to_basename");

      /* sanity clause */
      g_return_val_if_fail (message_id!=NULL, NULL);
      g_return_val_if_fail (message_id->str!=NULL, NULL);
      g_return_val_if_fail (buf!=NULL, NULL);
      g_return_val_if_fail (len>0, NULL);

      /* some characters in message-ids are illegal on older Windows boxes,
       * so we transform those illegal characters using URL encoding */
      for (in=message_id->str, out=buf; *in; ++in) {
            switch (*in) {
                  case '%': /* this is the escape character */
                  case '"': case '*': case '/': case ':': case '?': case '|':
                  case '\\': /* these are illegal on vfat, fat32 */
                        g_snprintf (out, len-(out-buf), "%%%02x", (int)*in);
                        out += 3;
                        break;
                  case '<': case '>': /* these are illegal too, but rather than encoding
                                         them, follow the convention of omitting them */
                        break;
                  default:
                        *out++ = *in;
            }
      }
      g_snprintf (out, len-(out-buf), ".msg");

      debug_exit ("acache_message_id_to_basename");
      return buf;
}

/**
 * Message-IDs are transformed via acache_message_id_to_basename()
 * to play nicely with some filesystems, so to extract the Message-ID
 * from a basename we need to reverse the transform.
 *
 * @return string length, or 0 on failure
 */
static int
acache_basename_to_message_id (char * buf, int len, const char * basename)
{
      const char * in;
      char * out;
      char * pch;
      char tmp_basename[PATH_MAX];
      debug_enter ("acache_basename_to_message_id");

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(basename), 0);
      g_return_val_if_fail (buf!=NULL, 0);
      g_return_val_if_fail (len>0, 0);

      /* remove the trailing ".msg" */
      g_strlcpy (tmp_basename, basename, sizeof(tmp_basename));
      if ((pch = g_strrstr (tmp_basename, ".msg")))
            *pch = '\0';
      g_strstrip (tmp_basename);

      /* transform */
      out = buf;
      *out++ = '<';
      for (in=tmp_basename; *in; ++in) {
            if (in[0]!='%' || !g_ascii_isxdigit(in[1]) || !g_ascii_isxdigit(in[2]))
                  *out++ = *in;
            else {
                  char buf[3];
                  buf[0] = *++in;
                  buf[1] = *++in;
                  buf[2] = '\0';
                  *out++ = (char) strtoul (buf, NULL, 16);
            }
      }
      *out++ = '>';
      *out = '\0';

      /* cleanup */
      debug_exit ("acache_basename_to_message_id");
      return out - buf;
}

static char*
acache_get_filename (char * buf, int len, const char * path, const PString * message_id)
{
      char basename[PATH_MAX] = { '\0' };

      /* sanity clause */
      g_return_val_if_fail (buf!=NULL, NULL);
      g_return_val_if_fail (len>0, NULL);
      g_return_val_if_fail (pstring_is_set (message_id),NULL);

      /* build the filename */
      *buf = '\0';
      if (acache_message_id_to_basename (basename, sizeof(basename), message_id)) {
            g_snprintf (buf, len, "%s%c%s", path, G_DIR_SEPARATOR, basename);
            pan_file_normalize_inplace (buf);
      }

      return is_nonempty_string(buf) ? buf : NULL;
}


/***
****
****  AcacheEntry Management
****
***/

static GHashTable*
acache_get_key_path_hash (const char * key_path)
{
      GHashTable * retval = NULL;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string (key_path), NULL);

      /* ensure there's a hash table for this path. */
      retval = g_hash_table_lookup (key_to_messageids, key_path);
      if (retval == NULL) {
            retval = g_hash_table_new_full (pstring_hash, pstring_equal, (GDestroyNotify)pstring_free, NULL);
            g_hash_table_insert (key_to_messageids, g_strdup(key_path), retval);
      }
      
      return retval;
}

static AcacheEntry*
acache_lookup_entry (const char* key_path, const PString * message_id)
{
      GHashTable * message_ids;

      g_return_val_if_fail (is_nonempty_string(key_path), NULL);
      g_return_val_if_fail (message_id!=NULL, NULL);
      g_return_val_if_fail (message_id->len>0, NULL);
      g_return_val_if_fail (message_id->str!=NULL, NULL);

      message_ids = acache_get_key_path_hash (key_path);

      return (AcacheEntry*) g_hash_table_lookup (message_ids, message_id);
}

static AcacheEntry*
acache_get_entry (const char * key_path, const PString * message_id)
{
      AcacheEntry * entry = NULL;
      GHashTable * message_ids;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(key_path), NULL);
      g_return_val_if_fail (pstring_is_set (message_id), NULL);

      message_ids = acache_get_key_path_hash (key_path);
      entry = g_hash_table_lookup (message_ids, message_id);
      if (entry == NULL)
      {
            PString * mid_dup = pstring_dup (message_id);

            entry = (AcacheEntry*) memchunk_alloc (acache_entry_chunk);
            entry->path = NULL;
            entry->refcount = 0;
            entry->message_id = mid_dup;
            entry->size = 0;
            entry->date = time (NULL);
            g_hash_table_replace (message_ids, mid_dup, entry);
            debug1 (DEBUG_ACACHE, "Added new entry %s", mid_dup->str);
      }

      return entry;
}

/**
 * This must be called from inside an entries_mutex lock.
 */
static void
acache_remove_entry (AcacheEntry * entry)
{
      GHashTable * message_ids;
      char filename[PATH_MAX] = { '\0' };

      /* sanity clause */
      g_return_if_fail (entry!=NULL);
      g_return_if_fail (entry->path!=NULL);
      g_return_if_fail (pstring_is_set (entry->message_id));

      /* notify listeners that this message is going away */
      debug2 (DEBUG_ACACHE, "Removing %s from cache with refcount %d", entry->message_id, entry->refcount);
      fire_bodies_removed ((const PString**)&entry->message_id, 1);

      /* remove the file */
      acache_get_filename (filename, sizeof(filename), entry->path->path, entry->message_id);
      debug1 (DEBUG_ACACHE, "unlinking [%s]", filename);
      unlink (filename);
      entry->path->size -= entry->size;

      /* remove it from the lookup table */
      message_ids = acache_get_key_path_hash (entry->path->key);
      g_hash_table_remove (message_ids, entry->message_id);
      entry->message_id = NULL;

      /* free the entry struct */
      memchunk_free (acache_entry_chunk, entry);
      entry = NULL;
}
/***
****
****
****
***/

static void
acache_path_free (AcachePath * path)
{
      g_free (path->key);
      g_free (path->path);
      g_free (path);
}

static int
get_message_id_from_file (char * msgid, int msgid_len, const char * filename)
{
      const char * basename;
      int retval = 0;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(filename), FALSE);
      g_return_val_if_fail (msgid!=NULL, FALSE);
      g_return_val_if_fail (msgid_len>0, FALSE);
      
      /* get the message-id */
      *msgid = '\0';
      basename = g_basename (filename);
      if (basename != NULL)
            retval = acache_basename_to_message_id (msgid, msgid_len, basename);

      return retval;
}

void
acache_init (void)
{
      Server *server;
      char * message_base;
      char * folder_base;
      char * path;
      GDir * dir;
      GError * err;
      debug_enter ("acache_init");

      /* sanity clause -- don't init twice */
      if (acache_entry_chunk != NULL)
            return;

      /* init the message id hash  */
      acache_entry_chunk = memchunk_new (sizeof(AcacheEntry), ACACHE_ENTRY_CHUNK_SIZE, FALSE);
      key_to_path = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)acache_path_free);
      key_to_messageids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy);

      /* add the folder server */
      server = server_new ();
      pstring_set (&server->address, "Mock Server for Folders", -1);
      pstring_copy (&server->name, &INTERNAL_SERVER_NAME);
      serverlist_add_server (server, NULL);

      /* ensure the folder directories exist */
      message_base = acache_get_message_base ();
      path = g_build_filename (message_base, ACACHE_DEFAULT_KEY, NULL);
      acache_add_path (ACACHE_DEFAULT_KEY, path);
      replace_gstr (&path, NULL);

      /* ensure pan.sent exists */
      folder_base = acache_get_folder_base ();
      path = g_build_filename (folder_base, PAN_SENT.str, NULL);
      pan_file_ensure_path_exists (path);
      replace_gstr (&path, NULL);

      /* ensure pan.sendlater exists */
      path = g_build_filename (folder_base, PAN_SENDLATER.str, NULL);
      pan_file_ensure_path_exists (path);
      replace_gstr (&path, NULL);

      /* add all the folder directories */
      err = NULL;
      dir = g_dir_open (folder_base, 0, &err);
      if (err != NULL)
      {
            log_add_va (LOG_ERROR, _("Error opening directory \"%s\": %s)"), folder_base, err->message);
            g_error_free (err);
      }
      else
      {
            const char * key;
            GPtrArray * folders = g_ptr_array_new ();

            /* add the folders */
            while ((key = g_dir_read_name (dir)))
            {
                  /* add the folder */
                  Group * folder = group_new (server, key);
                  group_set_is_folder (folder, TRUE);
                  g_ptr_array_add (folders, folder);
                  acache_add_folder (key);
            }
            g_dir_close (dir);

            if (folders->len)
                  server_add_groups (server, (Group**)folders->pdata, folders->len, NULL, NULL);

            g_ptr_array_free (folders, TRUE);
      }

      /* cleanup */
      g_free (folder_base);
      g_free (message_base);
      debug_exit ("acache_init");
}


void
acache_shutdown (void)
{
      if (key_to_messageids) {
            if (acache_flush_on_exit)
                  acache_expire_all ();
            g_hash_table_destroy (key_to_messageids);
            key_to_messageids = NULL;
      }

      if (key_to_path) {
            g_hash_table_destroy (key_to_path);
            key_to_path = NULL;
      }

      if (acache_entry_chunk) {
            memchunk_destroy (acache_entry_chunk);
            acache_entry_chunk = NULL;
      }
}

/***
****
****  EXPIRE
****
***/

static int
compare_ppEntry_ppEntry_by_youth (gconstpointer a, gconstpointer b, gpointer unused)
{
      const time_t atime = (**(AcacheEntry**)a).date;
      const time_t btime = (**(AcacheEntry**)b).date;
      return (int) -difftime (atime, btime);
}

void
acache_expire_messages (const char                * path_key,
                        const PString * const     * mids,
                        int                         mid_qty)
{
      /* sanity clause */
      g_return_if_fail (is_nonempty_string (path_key));
      g_return_if_fail (mid_qty >= 0);
      g_return_if_fail (mids != NULL);

      /* do the work */
      g_static_mutex_lock (&entries_mutex);
      {
            int i;

            for (i=0; i<mid_qty; ++i)
            {
                  AcacheEntry * entry = acache_lookup_entry (path_key, mids[i]);
                  if (entry != NULL)
                        acache_remove_entry (entry);
            }

            memchunk_clean (acache_entry_chunk);
      }
      g_static_mutex_unlock (&entries_mutex);
}

static int
acache_expire_to_size (AcachePath * apath, double max_megs)
{
      int files_removed = 0;
      const size_t cache_max = (size_t) max_megs * (1024 * 1024);
      debug_enter ("acache_expire_to_size");

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

      g_static_mutex_lock (&entries_mutex);
      {
            debug3 (DEBUG_ACACHE, "expiring to %lu; current size is %lu (%ld free)", cache_max, apath->size, (long)cache_max-(long)apath->size);

            if (cache_max < apath->size)
            {
                  int i;
                  GPtrArray * entries;

                  /* get an array of files sorted from oldest to youngest */
                  entries = g_ptr_array_new ();
                  pan_hash_to_ptr_array (acache_get_key_path_hash(apath->key), entries);
                  g_ptr_array_sort_with_data (entries, compare_ppEntry_ppEntry_by_youth, NULL);

                  /* Start blowing away files */
                  for (i=entries->len; i>0 && cache_max<apath->size; --i) {
                        AcacheEntry * entry = (AcacheEntry*) g_ptr_array_index (entries, i-1);
                        if (entry->path==apath && entry->refcount<=0) {
                              acache_remove_entry (entry);
                              ++files_removed;
                        }
                  }

                  /*  cleanup */
                  g_ptr_array_free (entries, TRUE);
                  memchunk_clean (acache_entry_chunk);
            }
      }
      g_static_mutex_unlock (&entries_mutex);

      /* done */
      debug_exit ("acache_expire_to_size");
      return files_removed;
}

int 
acache_expire (void)
{
      AcachePath * apath = (AcachePath*) g_hash_table_lookup (key_to_path, ACACHE_DEFAULT_KEY);
      const double desired_size = acache_max_megs * 0.8;
      return acache_expire_to_size (apath, desired_size);
}

int
acache_expire_all (void)
{
      AcachePath * apath;
      int retval;

      g_message (_("Flushing article cache... "));
            apath = (AcachePath*) g_hash_table_lookup (key_to_path, ACACHE_DEFAULT_KEY);
      retval = acache_expire_to_size (apath, 0.0);
      g_message (_("%d files erased."), retval);
      fflush (NULL);

      return retval;
}

/***
****
****  CHECKIN / CHECKOUT
****
***/

/* this must be called inside an entries_mutex lock */
static void
acache_update_refcount_nolock (const char                * path_key,
                               const PString * const     * mids,
                               int                         mid_qty,
                               int                         inc)
{
      int i;
      debug_enter ("acache_update_refcount_nolock");

      g_return_if_fail (mid_qty >= 1);
      g_return_if_fail (mids != NULL);

      for (i=0; i<mid_qty; ++i)
      {
            AcacheEntry * entry = acache_get_entry (path_key, mids[i]);

            /* If we're checking it out,
             * then move it to the safe end of the least-recently-used kill heuristic */
            if (inc > 0)
                  entry->date = time (NULL);

            entry->refcount += inc;
            debug3 (DEBUG_ACACHE, "%s refcount - inc by %d to %d", entry->message_id, inc, entry->refcount);
      }

      debug_exit ("acache_update_refcount_nolock");
}

void
acache_checkout (const char               * key,
                 const PString * const    * mids,
                 int                        mid_qty)
{
      debug_enter ("acache_checkout");

      /* sanity clause */
      g_return_if_fail (is_nonempty_string(key));
      g_return_if_fail (mids!=NULL);
      g_return_if_fail (mid_qty >= 1);

      /* ref the entries */
      g_static_mutex_lock (&entries_mutex);
      acache_update_refcount_nolock (key, mids, mid_qty, 1);
      g_static_mutex_unlock (&entries_mutex);

      debug_exit ("acache_checkout");
}

void
acache_checkin (const char                * key,
                const PString * const     * mids,
                int                         mid_qty)
{
      debug_enter ("acache_checkin");

      /* sanity clause */
      g_return_if_fail (is_nonempty_string (key));
      g_return_if_fail (mids!=NULL);
      g_return_if_fail (mid_qty >= 1);

      /* unref the entries */
      g_static_mutex_lock (&entries_mutex);
      acache_update_refcount_nolock (key, mids, mid_qty, -1);
      g_static_mutex_unlock (&entries_mutex);

      acache_expire ();

      debug_exit ("acache_checkin");
}

/***
****
****  ADD FILES
****
***/

void
acache_set_message (const char        * key,
                    const PString     * message_id,
                    const char        * message,
                    guint               message_len)
{
            FILE * fp = NULL;
      char filename[PATH_MAX];
      AcachePath * apath;

      /* sanity clause */
      g_return_if_fail (is_nonempty_string(key));
      g_return_if_fail (pstring_is_set(message_id));
      g_return_if_fail (is_nonempty_string(message));
      g_return_if_fail (message_len > 0);

      /* get the path */
      apath = (AcachePath*) g_hash_table_lookup (key_to_path, key);
      g_return_if_fail (apath!=NULL);

      /* find out where to write the message */
      if (acache_get_filename (filename, sizeof(filename), apath->path, message_id))
            fp = fopen (filename, "wb+");

      if (fp != NULL)
      {
            const size_t bytes_written = fwrite (message, sizeof(char), message_len, fp);
            fclose (fp);

            if (bytes_written < message_len)
            {
                  /* couldn't save the whole message */
                  char * path = g_path_get_dirname (filename);
                  log_add_va (LOG_ERROR, _("Error saving article \"%s\" (is %s full?)"), message_id, path);
                  g_free (path);
            }
            else
            {
                  AcacheEntry * entry;

                  /* file an entry for this message */
                  g_static_mutex_lock (&entries_mutex);
                  entry = acache_get_entry (key, message_id);
                  entry->size = message_len;
                  entry->date = time (NULL);
                  entry->path = apath;
                  g_static_mutex_unlock (&entries_mutex);

                  /* notify everyone that this message has been added */
                  fire_bodies_added (&message_id, 1);

                  /* if the acache is too big, purge the old stuff */
                  apath->size += message_len;
                  acache_expire ();
            }
      }
}


/***
****
****  GETTING MESSAGES
****
***/

static GMimeStream*
acache_get_message_file_stream (const AcacheEntry * entry)
{
      GMimeStream * retval = NULL;
      char filename[PATH_MAX];

      /* sanity clause */
      g_return_val_if_fail (entry!=NULL, NULL);
      if (entry->path==NULL) return NULL; /* message has entry, but no body */

      /* open the file */
      if (acache_get_filename (filename, sizeof(filename), entry->path->path, entry->message_id))
      {
            FILE * fp;
            errno = 0;
            fp = fopen (filename, "rb");
            if (!fp)
                  log_add_va (LOG_ERROR, _("Error opening file \"%s\": %s"), filename, g_strerror(errno));
            else
            {
                  GMimeStream * file_stream = g_mime_stream_file_new (fp);
                  retval = g_mime_stream_buffer_new (file_stream, GMIME_STREAM_BUFFER_BLOCK_READ);
                  g_object_unref (file_stream);
            }
      }

      return retval;
}

static GMimeStream*
acache_get_message_mem_stream (const AcacheEntry * entry)
{
      GMimeStream * retval = NULL;
      char filename[PATH_MAX];

      /* sanity clause */
      g_return_val_if_fail (entry!=NULL, NULL);
      if (entry->path==NULL) return NULL; /* message has entry, but no body */

      /* open the file */
      if (acache_get_filename (filename, sizeof(filename), entry->path->path, entry->message_id))
      {
            gsize len = 0;
            char * buf = NULL;
            GError * err = NULL;

            if (g_file_get_contents (filename, &buf, &len, &err)) {
                  retval = g_mime_stream_mem_new_with_buffer (buf, len);
                  g_free (buf);
            } else {
                  log_add_va (LOG_ERROR, _("Error reading file \"%s\": %s"), filename, err->message);
                  g_error_free (err);
            }
      }

      return retval;
}

GMimeMessage*
acache_get_message  (const char                * path_key,
                     const PString * const     * mids,
                     int                         mid_qty)
{
      int i;
      int stream_qty;
      GMimeStream ** streams;
      GMimeMessage * retval = NULL;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(path_key), NULL);
      g_return_val_if_fail (mid_qty>0, NULL);
      g_return_val_if_fail (mids!=NULL, NULL);

      /* get streams */
      streams = g_newa (GMimeStream*, mid_qty);
      for (i=stream_qty=0; i<mid_qty; ++i)
      {
            GMimeStream * stream;
            const AcacheEntry * entry = acache_lookup_entry (path_key, mids[i]);
            if (entry == NULL)
                  break;

            stream = mid_qty>2
                  ? acache_get_message_file_stream (entry)
                  : acache_get_message_mem_stream (entry);
            if (!stream)
                  break;

            streams[stream_qty++] = stream;
      }

      /* build the message */
      if (stream_qty == mid_qty)
            retval = pan_g_mime_parser_construct_message (streams, stream_qty);

      /* cleanup */
      for (i=0; i<stream_qty; ++i)
            g_object_unref (streams[i]);

      return retval;
}

gboolean
acache_has_message (const char        * path_key,
                    const PString     * message_id)
{
      gboolean retval = FALSE;
      AcacheEntry * entry = NULL;

      /* sanity clause */
      g_return_val_if_fail (is_nonempty_string(path_key), FALSE);
      g_return_val_if_fail (pstring_is_set(message_id), FALSE);

      /* lookup the message */
      entry = acache_lookup_entry (path_key, message_id);
      if (entry != NULL)
            retval = entry->size != 0;

      return retval;
}

/***
****
****  PATHS
****
***/

typedef struct
{
      AcachePath * path;
      AcachePathForeachFunc func;
      gpointer user_data;
}
ForeachStruct;

static void
acache_path_hash_foreach (gpointer message_id,
                          gpointer entry_gpointer,
                          gpointer user_data)
{
      ForeachStruct * fs = (ForeachStruct*) user_data;
      AcacheEntry * entry = (AcacheEntry*) entry_gpointer;

      if (fs->path == entry->path)
      {
            GMimeStream * stream = acache_get_message_mem_stream (entry);
            GMimeMessage * message = pan_g_mime_parser_construct_message (&stream, 1);

            /* call the user's function */
            (*fs->func)(message, fs->user_data);

            /* cleanup */
            g_object_unref (message);
            g_object_unref (stream);
      }
}

void
acache_path_foreach (const char             * key,
                     AcachePathForeachFunc    func,
                     gpointer                 user_data)
{
      AcachePath * apath;
      ForeachStruct tmp;

      /* get the scratch directory path */
      debug1 (DEBUG_ACACHE, "Calling acache_path_foreach for key \"%s\"", key);
      apath = (AcachePath*) g_hash_table_lookup (key_to_path, key);
      g_return_if_fail (apath!=NULL);

      tmp.path = apath;
      tmp.func = func;
      tmp.user_data = user_data;
      g_hash_table_foreach (acache_get_key_path_hash(key), acache_path_hash_foreach, &tmp);
}

void
acache_add_folder (const char * key)
{
      char * folder_base = acache_get_folder_base ();
      char * path = g_build_filename (folder_base, key, NULL);

      acache_add_path (key, path);

      g_free (path);
      g_free (folder_base);
}

static void
acache_add_path (const char * key,
                 const char * path)
{
      int qty = 0;
      GDir * dir;
      GError * err;
      AcachePath * apath;
      debug_enter ("acache_add_path");

      /* sanity clause */
      g_return_if_fail (is_nonempty_string(key));
      g_return_if_fail (is_nonempty_string(path));
      g_return_if_fail (pan_file_ensure_path_exists(path));

      /* make the path entry */
      debug2 (DEBUG_ACACHE, "Adding path: key \"%s\", path \"%s\"", key, path);
      apath = g_new (AcachePath, 1);
      apath->key = g_strdup (key);
      apath->path = g_strdup (path);
      apath->size = 0;
      g_hash_table_replace (key_to_path, apath->key, apath);

      /* walk through the directory */
      err = NULL;
      dir = g_dir_open (path, 0, &err);
      if (err != NULL)
      {
            log_add_va (LOG_ERROR, _("Error opening directory \"%s\": %s)"), path, err->message);
            g_error_free (err);
      }
      else
      {
            const char * fname;
            char str[2048];
            char filename[PATH_MAX];

            while ((fname = g_dir_read_name (dir)))
            {
                  struct stat stat_p;

                  g_snprintf (filename, sizeof(filename), "%s%c%s", path, G_DIR_SEPARATOR, fname);
                  if (stat (filename, &stat_p) == 0)
                  {
                        int len = get_message_id_from_file (str, sizeof(str), filename);

                        if (len != 0)
                        {
                              const PString mid = pstring_shallow (str, len);

                              /*
                               * FIXME: this may segfault if acache 
                               * is shut down while this thread is 
                               * still running.
                               */

                              AcacheEntry * entry = acache_get_entry (key, &mid);
                              entry->path = apath;
                              entry->size = stat_p.st_size;
                              entry->date = stat_p.st_mtime;
                              entry->refcount = 0;

                              apath->size += entry->size;
                              ++qty;
                        }
                  }
            }

            g_dir_close (dir);
      }

      log_add_va (LOG_INFO, _("Directory \"%s\" contains %.1f MB in %d files"),
            apath->path, (double)apath->size/(1024*1024), qty);

      debug_exit ("acache_add_path");
}

Generated by  Doxygen 1.6.0   Back to index