"SfR Fresh" - the SfR Freeware/Shareware Archive

Member "pan-0.133/pan/data/article-cache.cc" of archive pan-0.133.tar.gz:


As a special service "SfR Fresh" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. That can be also achieved for any archive member file by clicking within an archive contents listing on the first character of the file(path) respectively on the according byte size field.
    1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
    2 /*
    3  * Pan - A Newsreader for Gtk+
    4  * Copyright (C) 2002-2006  Charles Kerr <charles@rebelbase.com>
    5  *
    6  * This program is free software; you can redistribute it and/or modify
    7  * it under the terms of the GNU General Public License as published by
    8  * the Free Software Foundation; version 2 of the License.
    9  *
   10  * This program is distributed in the hope that it will be useful,
   11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13  * GNU General Public License for more details.
   14  *
   15  * You should have received a copy of the GNU General Public License
   16  * along with this program; if not, write to the Free Software
   17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   18  */
   19 
   20 #include <config.h>
   21 
   22 extern "C"
   23 {
   24   #include <errno.h>
   25   #include <sys/types.h>
   26   #include <sys/stat.h>
   27   #include <unistd.h>
   28   #include <dirent.h>
   29 
   30   #include <glib.h>
   31   #include <glib/gi18n.h>
   32   #include <gmime/gmime.h>
   33 }
   34 
   35 #include <pan/general/debug.h>
   36 #include <pan/general/file-util.h>
   37 #include <pan/general/macros.h>
   38 #include <pan/general/messages.h>
   39 #include <pan/general/log.h>
   40 #include <pan/general/string-view.h>
   41 #include <pan/usenet-utils/mime-utils.h>
   42 #include "article.h"
   43 #include "article-cache.h"
   44 
   45 using namespace pan;
   46 
   47 /*****
   48 ******
   49 *****/
   50 
   51 namespace
   52 {
   53    /**
   54     * Some characters in message-ids don't work well in filenames,
   55     * so we transform them to a safer name.
   56     */
   57    char*
   58    message_id_to_filename (char * buf, int len, const StringView& mid)
   59    {
   60       // sanity clause
   61       pan_return_val_if_fail (!mid.empty(), 0);
   62       pan_return_val_if_fail (buf!=0, NULL);
   63       pan_return_val_if_fail (len>0, NULL);
   64 
   65       // some characters in message-ids are illegal on older Windows boxes,
   66       // so we transform those illegal characters using URL encoding
   67       char * out = buf;
   68       for (const char *in=mid.begin(), *end=mid.end(); in!=end; ++in) {
   69          switch (*in) {
   70             case '%': /* this is the escape character */
   71             case '"': case '*': case '/': case ':': case '?': case '|':
   72             case '\\': /* these are illegal on vfat, fat32 */
   73                g_snprintf (out, len-(out-buf), "%%%02x", (int)*in);
   74                out += 3;
   75                break;
   76             case '<': case '>': /* these are illegal too, but rather than encoding
   77                                    them, follow the convention of omitting them */
   78                break;
   79             default:
   80                *out++ = *in;
   81          }
   82       }
   83 
   84       g_snprintf (out, len-(out-buf), ".msg");
   85       return buf;
   86    }
   87 
   88    /**
   89     * Message-IDs are transformed via message_id_to_filename()
   90     * to play nicely with some filesystems, so to extract the Message-ID
   91     * from a filename we need to reverse the transform.
   92     *
   93     * @return string length, or 0 on failure
   94     */
   95    int
   96    filename_to_message_id (char * buf, int len, const char * basename)
   97    {
   98       const char * in;
   99       char * out;
  100       char * pch;
  101       char tmp_basename[PATH_MAX];
  102 
  103       // sanity clause
  104       pan_return_val_if_fail (basename && *basename, 0);
  105       pan_return_val_if_fail (buf!=NULL, 0);
  106       pan_return_val_if_fail (len>0, 0);
  107 
  108       // remove the trailing ".msg"
  109       g_strlcpy (tmp_basename, basename, sizeof(tmp_basename));
  110       if ((pch = g_strrstr (tmp_basename, ".msg")))
  111          *pch = '\0';
  112       g_strstrip (tmp_basename);
  113 
  114       // transform
  115       out = buf;
  116       *out++ = '<';
  117       for (in=tmp_basename; *in; ++in) {
  118          if (in[0]!='%' || !g_ascii_isxdigit(in[1]) || !g_ascii_isxdigit(in[2]))
  119             *out++ = *in;
  120          else {
  121             char buf[3];
  122             buf[0] = *++in;
  123             buf[1] = *++in;
  124             buf[2] = '\0';
  125             *out++ = (char) strtoul (buf, NULL, 16);
  126          }
  127       }
  128       *out++ = '>';
  129       *out = '\0';
  130 
  131       return out - buf;
  132    }
  133 };
  134 
  135 /*****
  136 ******
  137 *****/
  138 
  139 ArticleCache :: ArticleCache (const StringView& path, size_t max_megs):
  140    _path (path.str, path.len),
  141    _max_megs (max_megs),
  142    _current_bytes (0ul)
  143 {
  144    GError * err = NULL;
  145    GDir * dir = g_dir_open (_path.c_str(), 0, &err);
  146    if (err != NULL)
  147    {
  148       Log::add_err_va (_("Error opening directory: \"%s\": %s"), _path.c_str(), err->message);
  149       g_clear_error (&err);
  150    }
  151    else
  152    {
  153       char filename[PATH_MAX];
  154       const char * fname;
  155       while ((fname = g_dir_read_name (dir)))
  156       {
  157          struct stat stat_p;
  158          g_snprintf (filename, sizeof(filename), "%s%c%s", _path.c_str(), G_DIR_SEPARATOR, fname);
  159          if (!stat (filename, &stat_p))
  160          {
  161             char str[2048];
  162             const int len (filename_to_message_id (str, sizeof(str), fname));
  163             if (len != 0)
  164             {
  165                MsgInfo info;
  166                info._message_id = StringView (str, len);
  167                info._size = stat_p.st_size;
  168                info._date = stat_p.st_mtime;
  169                _current_bytes += info._size;
  170                _mid_to_info.insert (mid_to_info_t::value_type (info._message_id, info));
  171             }
  172          }
  173       }
  174       g_dir_close (dir);
  175       debug ("loaded " << _mid_to_info.size() << " articles into cache from " << _path);
  176    }
  177 }
  178 
  179 ArticleCache :: ~ArticleCache ()
  180 {
  181 }
  182 
  183 /*****
  184 ******
  185 *****/
  186 
  187 void
  188 ArticleCache :: fire_added (const Quark& mid)
  189 {
  190   for (listeners_t::iterator it(_listeners.begin()), end(_listeners.end()); it!=end; )
  191     (*it++)->on_cache_added (mid);
  192 }
  193 
  194 void
  195 ArticleCache :: fire_removed (const quarks_t& mids)
  196 {
  197   for (listeners_t::iterator it(_listeners.begin()), end(_listeners.end()); it!=end; )
  198     (*it++)->on_cache_removed (mids);
  199 }
  200 
  201 /*****
  202 ******
  203 *****/
  204 
  205 bool
  206 ArticleCache :: contains (const Quark& mid) const
  207 {
  208   return _mid_to_info.find (mid) != _mid_to_info.end();
  209 }
  210 
  211 char*
  212 ArticleCache :: get_filename (char * buf, int buflen, const Quark& mid) const
  213 {
  214    char basename[PATH_MAX];
  215    *buf = '\0';
  216    message_id_to_filename (basename, sizeof(basename), mid.to_view());
  217    g_snprintf (buf, buflen, "%s%c%s", _path.c_str(), G_DIR_SEPARATOR, basename);
  218    return buf && *buf ? buf : 0;
  219 };
  220 
  221 bool
  222 ArticleCache :: add (const Quark& message_id, const StringView& article)
  223 {
  224   debug ("adding " << message_id << ", which is " << article.len << " bytes long");
  225 
  226   pan_return_val_if_fail (!message_id.empty(), false);
  227   pan_return_val_if_fail (!article.empty(), false);
  228 
  229   FILE * fp = 0;
  230   char filename[PATH_MAX];
  231   if (get_filename (filename, sizeof(filename), message_id))
  232     fp = fopen (filename, "wb+");
  233 
  234   if (!fp)
  235   {
  236       Log::add_err_va (_("Unable to save \"%s\" %s"),
  237                        filename, file::pan_strerror(errno));
  238       return false;
  239   }
  240   else
  241   {
  242     const size_t bytes_written (fwrite (article.str, sizeof(char), article.len, fp));
  243     fclose (fp);
  244 
  245     if (bytes_written < article.len)
  246     {
  247       Log::add_err_va (_("Unable to save \"%s\" %s"),
  248                        filename, file::pan_strerror(errno));
  249       return false;
  250     }
  251     else
  252     {
  253       MsgInfo info;
  254       info._message_id = message_id;
  255       info._size = article.len;
  256       info._date = time(0);
  257       _mid_to_info.insert (mid_to_info_t::value_type (info._message_id, info));
  258       fire_added (message_id);
  259 
  260       _current_bytes += info._size;
  261       resize ();
  262     }
  263   }
  264 
  265   return true;
  266 }
  267 
  268 /***
  269 ****
  270 ***/
  271 
  272 void
  273 ArticleCache :: reserve (const mid_sequence_t& mids)
  274 {
  275   foreach_const (mid_sequence_t, mids, it)
  276     ++_locks[*it];
  277 }
  278 
  279 void
  280 ArticleCache :: release (const mid_sequence_t& mids)
  281 {
  282   foreach_const (mid_sequence_t, mids, it)
  283     if (!--_locks[*it])
  284       _locks.erase (*it);
  285 }
  286 
  287 /***
  288 ****
  289 ***/
  290 
  291 void
  292 ArticleCache :: resize ()
  293 {
  294   // let's shrink it to 80% of the maximum size
  295   const double buffer_zone (0.8);
  296   guint64 max_bytes (_max_megs * 1024 * 1024);
  297   max_bytes = (guint64) ((double)max_bytes * buffer_zone);
  298   resize (max_bytes);
  299 }
  300 
  301 void
  302 ArticleCache :: clear ()
  303 {
  304   resize (0);
  305 }
  306 
  307 void
  308 ArticleCache :: resize (guint64 max_bytes)
  309 {
  310   quarks_t removed;
  311   if (_current_bytes > max_bytes)
  312   {
  313     // sort from oldest to youngest
  314     typedef std::set<MsgInfo, MsgInfoCompare> sorted_info_t;
  315     sorted_info_t si;
  316     for (mid_to_info_t::const_iterator it=_mid_to_info.begin(), end=_mid_to_info.end(); it!=end; ++it)
  317       si.insert (it->second);
  318 
  319     // start blowing away files
  320     for (sorted_info_t::const_iterator it=si.begin(), end=si.end(); _current_bytes>max_bytes && it!=end; ++it) {
  321       const Quark& mid (it->_message_id);
  322       if (_locks.find(mid) == _locks.end()) {
  323         char buf[PATH_MAX];
  324         get_filename (buf, sizeof(buf), mid);
  325         unlink (buf);
  326         _current_bytes -= it->_size;
  327         removed.insert (mid);
  328         debug ("removing [" << mid << "] as we resize the queue");
  329         _mid_to_info.erase (mid);
  330       }
  331     }
  332   }
  333 
  334   debug ("cache expired " << removed.size() << " articles, "
  335          "has " << _mid_to_info.size() << " active "
  336          "and " << _locks.size() << " locked.");
  337 
  338   if (!removed.empty())
  339     fire_removed (removed);
  340 }
  341 
  342 /****
  343 *****
  344 *****  Getting Messages
  345 *****
  346 ****/
  347 
  348 /*private*/ GMimeStream*
  349 ArticleCache :: get_message_file_stream (const Quark& mid) const
  350 {
  351    GMimeStream * retval = NULL;
  352 
  353    /* open the file */
  354    char filename[PATH_MAX];
  355    if (get_filename (filename, sizeof(filename), mid))
  356    {
  357       errno = 0;
  358       FILE * fp = fopen (filename, "rb");
  359       if (!fp)
  360          Log::add_err_va (_("Error opening file \"%s\" %s"), filename, file::pan_strerror(errno));
  361       else {
  362          GMimeStream * file_stream = g_mime_stream_file_new (fp);
  363          retval = g_mime_stream_buffer_new (file_stream, GMIME_STREAM_BUFFER_BLOCK_READ);
  364          g_object_unref (file_stream);
  365       }
  366    }
  367 
  368    debug ("file stream for " << mid << ": " << retval);
  369    return retval;
  370 }
  371 
  372 /*private*/ GMimeStream*
  373 ArticleCache :: get_message_mem_stream (const Quark& mid) const
  374 {
  375    debug ("mem stream got quark " << mid);
  376    GMimeStream * retval (0);
  377 
  378    char filename[PATH_MAX];
  379    if (get_filename (filename, sizeof(filename), mid))
  380    {
  381       debug ("mem stream loading filename " << filename);
  382       gsize len (0);
  383       char * buf (0);
  384       GError * err (0);
  385 
  386       if (g_file_get_contents (filename, &buf, &len, &err)) {
  387          debug ("got the contents, calling mem_new_with_buffer");
  388          retval = g_mime_stream_mem_new_with_buffer (buf, len);
  389          g_free (buf);
  390       } else {
  391          Log::add_err_va (_("Error reading file \"%s\": %s"), filename, err->message);
  392          g_clear_error (&err);
  393       }
  394    }
  395 
  396    debug ("mem stream for " << mid << ": " << retval);
  397    return retval;
  398 }
  399 
  400 GMimeMessage*
  401 ArticleCache :: get_message (const mid_sequence_t& mids)
  402 {
  403    debug ("trying to get a message with " << mids.size() << " parts");
  404    GMimeMessage * retval = NULL;
  405 
  406    // load the streams
  407    typedef std::vector<GMimeStream*> streams_t;
  408    streams_t streams;
  409    //const bool in_memory (mids.size() <= 2u);
  410    const bool in_memory (true); // workaround for bug #439841
  411    foreach_const (mid_sequence_t, mids, it) {
  412       const Quark mid (*it);
  413       GMimeStream * stream (0);
  414       if (this->contains (*it))
  415         stream = in_memory
  416           ? get_message_mem_stream (mid)
  417           : get_message_file_stream (mid);
  418       if (stream)
  419         streams.push_back (stream);
  420    }
  421 
  422    // build the message
  423    if (!streams.empty())
  424      retval = mime :: construct_message (&streams.front(), streams.size());
  425 
  426    // cleanup
  427    foreach (streams_t, streams, it)
  428      g_object_unref (*it);
  429 
  430    debug ("returning " << retval);
  431    return retval;
  432 }
  433 
  434 ArticleCache :: strings_t
  435 ArticleCache :: get_filenames (const mid_sequence_t& mids)
  436 {
  437   strings_t ret;
  438   char filename[PATH_MAX];
  439   foreach_const (mid_sequence_t, mids, it)
  440     if (get_filename (filename, sizeof(filename), *it))
  441       ret.push_back (filename);
  442   return ret;
  443 }