aboutsummaryrefslogblamecommitdiffstats
path: root/camel/providers/nntp/camel-nntp-newsrc.c
blob: 309e1f45c83bb0c23eefb3e78b0c2f8c2474ca59 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                                                           

                                             
  
                                                     
  



                                                                        
  







                                                                        
  

   

                    
                   


                 



                   
                    
                   
                  
                  
 
                              
                                       
 

                                                                       
 













                            
                       
                     
  
 
 
                    
                                                                                                  







                                                                              





                             
          

                                                                                             
                                              




                                                                                       
















                                                                                          





















































































                                                                                                         
                                                                                            

                           


                                  

                                                                 




                                                                                       

                                    
 
                   

 



                                                                                         


                                  

                                                                 




                                                                                    

                                    
 
                   

 
    
                                                                                              




                                                                         
                                                                                                       











                                                      
                                  

                                                                 

                                                                                   
                                    


        
                                                                                             


                           
                        
 
                                  
                                                                 







                                                                                         


                 

                                    


                     


                                                                                       





                                                                 

                    
                                        
         



                                    




                                                                                   




                                                                 








                                                                       

                                    




                                                                                     


                                  
 
                                                                 







                                                                        

                                    

 




                                 
                                                                                












                                                                                      

                                            

                                  


                                          
                                             

                                                               

                                    







                                                               

                                            

                                  





                                                               

                                    



















                                                                                                 





                                                                         












                                                                                    















                                                                                 
                                           









                                                                  

                                  


                                  

                                  


                                                                         

                                    






                                                

                                  

                                  

                                            
                       
         


                                                                                  
                                            


                       
                              


                                                    
 





                                                                 
                               















                                                 
                    


                                                                          






























                                                                                   







































                                                                                  



                                                      



                                       
                                




                                                                               
 

                                            
                                                                    
                                     
        
                                                    


                                                                  
 









                                                                                          
 




                                                          
         
                                     
 







                                                           


                      
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Chris Toshok <toshok@ximian.com>
 *
 *  Copyright 2000-2003 Ximian, Inc. (www.ximian.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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 Street #330, Boston, MA 02111-1307, USA.
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include "camel-nntp-newsrc.h"
#include <camel/camel-folder-summary.h>

#define NEWSRC_LOCK(f, l) (g_mutex_lock(((CamelNNTPNewsrc *)f)->l))
#define NEWSRC_UNLOCK(f, l) (g_mutex_unlock(((CamelNNTPNewsrc *)f)->l))

typedef struct {
    guint low;
    guint high;
} ArticleRange;

typedef struct {
    char *name;
    GArray *ranges;
    gboolean subscribed;
} NewsrcGroup;

struct CamelNNTPNewsrc {
    gchar *filename;
    GHashTable *groups;
    gboolean dirty;
    GMutex *lock;
};


static NewsrcGroup *
camel_nntp_newsrc_group_add (CamelNNTPNewsrc *newsrc, const char *group_name, gboolean subscribed)
{
    NewsrcGroup *new_group = g_malloc(sizeof(NewsrcGroup));

    new_group->name = g_strdup(group_name);
    new_group->subscribed = subscribed;
    new_group->ranges = g_array_new (FALSE, FALSE, sizeof (ArticleRange));

    g_hash_table_insert (newsrc->groups, new_group->name, new_group);

    newsrc->dirty = TRUE;

    return new_group;
}

static int
camel_nntp_newsrc_group_get_highest_article_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group)
{
    if (!group || group->ranges->len == 0)
        return 0;

    return g_array_index(group->ranges, ArticleRange, group->ranges->len - 1).high;
}

static int
camel_nntp_newsrc_group_get_num_articles_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group)
{
    int i;
    int count = 0;

    if (group == NULL)
        return 0;

    for (i = 0; i < group->ranges->len; i ++)
        count += (g_array_index(group->ranges, ArticleRange, i).high -
              g_array_index(group->ranges, ArticleRange, i).low) + 1;

    return count;
}


static void
camel_nntp_newsrc_group_mark_range_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group, long low, long high)
{
    int i;

    if (group->ranges->len == 1
        && g_array_index (group->ranges, ArticleRange, 0).low == 0
        && g_array_index (group->ranges, ArticleRange, 0).high == 0) {
        g_array_index (group->ranges, ArticleRange, 0).low = low;
        g_array_index (group->ranges, ArticleRange, 0).high = high;

        newsrc->dirty = TRUE;
    }
    else  {
        ArticleRange tmp_range;

        for (i = 0; i < group->ranges->len; i ++) {
            guint range_low = g_array_index (group->ranges, ArticleRange, i).low;
            guint range_high = g_array_index (group->ranges, ArticleRange, i).high;
            
            /* if it's already part of a range, return immediately. */
            if (low >= range_low &&
                low <= range_high &&
                high >= range_low &&
                high <= range_high) {
                return;
            }
            /* if we have a new lower bound for this range, set it. */
            else if (low <= range_low
                 && high >= range_low
                 && high <= range_high) {
                g_array_index (group->ranges, ArticleRange, i).low = low;
                newsrc->dirty = TRUE;
                return;
            }
            /* if we have a new upper bound for this range, set it. */
            else if (high >= range_high
                 && low >= range_low
                 && low <= range_high) {
                g_array_index (group->ranges, ArticleRange, i).high = high;
                newsrc->dirty = TRUE;
                return;
            }
            /* if we would be inserting another range that
                           starts one index higher than an existing
                           one, make the upper value of the existing
                           range the upper value of the new one. */
            else if (low == range_high + 1) {
                g_array_index (group->ranges, ArticleRange, i).high = high;
                newsrc->dirty = TRUE;
                return;
            }
            /* if we would be inserting another range that
                           ends one index lower than an existing one,
                           group the existing range by setting its low
                           to the new low */
            else if (high == range_low - 1) {
                g_array_index (group->ranges, ArticleRange, i).low = low;
                newsrc->dirty = TRUE;
                return;
            }
            /* if the range lies entirely outside another
                           range, doesn't coincide with it's
                           endpoints, and has lower values, insert it
                           into the middle of the list. */
            else if (low < range_low
                 && high < range_low) {
                tmp_range.low = low;
                tmp_range.high = high;

                group->ranges = g_array_insert_val (group->ranges, i, tmp_range);
                newsrc->dirty = TRUE;

                return;
            }
        }

        /* if we made it here, the range needs to go at the end */
        tmp_range.low = low;
        tmp_range.high = high;
        group->ranges = g_array_append_val (group->ranges, tmp_range);
        newsrc->dirty = TRUE;
    } 
}

int
camel_nntp_newsrc_get_highest_article_read (CamelNNTPNewsrc *newsrc, const char *group_name)
{
    NewsrcGroup *group;
    int ret;

    NEWSRC_LOCK(newsrc, lock);

    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group)
        ret = camel_nntp_newsrc_group_get_highest_article_read (newsrc, group);
    else
        ret = 0;

    NEWSRC_UNLOCK(newsrc, lock);

    return ret;
}

int
camel_nntp_newsrc_get_num_articles_read (CamelNNTPNewsrc *newsrc, const char *group_name)
{
    NewsrcGroup *group;
    int ret;

    NEWSRC_LOCK(newsrc, lock);

    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group)
        ret = camel_nntp_newsrc_group_get_num_articles_read (newsrc, group);
    else
        ret = 0;

    NEWSRC_UNLOCK(newsrc, lock);

    return ret;
}

void
camel_nntp_newsrc_mark_article_read (CamelNNTPNewsrc *newsrc, const char *group_name, int num)
{
    camel_nntp_newsrc_mark_range_read (newsrc, group_name, num, num);
}

void
camel_nntp_newsrc_mark_range_read(CamelNNTPNewsrc *newsrc, const char *group_name, long low, long high)
{
    NewsrcGroup *group;

    /* swap them if they're in the wrong order. */
    if (low > high) {
        long tmp;

        tmp = high;
        high = low;
        low = tmp;
    }

    NEWSRC_LOCK(newsrc, lock);
    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group)
        camel_nntp_newsrc_group_mark_range_read (newsrc, group, low, high);
    NEWSRC_UNLOCK(newsrc, lock);
}

gboolean
camel_nntp_newsrc_article_is_read (CamelNNTPNewsrc *newsrc, const char *group_name, long num)
{
    int i;
    NewsrcGroup *group;
    int ret = FALSE;

    NEWSRC_LOCK(newsrc, lock);
    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group) {
        for (i = 0; i < group->ranges->len; i++) {
            if (num >= g_array_index (group->ranges, ArticleRange, i).low && 
                num <= g_array_index (group->ranges, ArticleRange, i).high) {
                ret = TRUE;
                break;
            }
        }
    }

    NEWSRC_UNLOCK(newsrc, lock);

    return FALSE;
}

gboolean  
camel_nntp_newsrc_group_is_subscribed (CamelNNTPNewsrc *newsrc, const char *group_name)
{
    NewsrcGroup *group;
    int ret = FALSE;

    NEWSRC_LOCK(newsrc, lock);

    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group) {
        ret = group->subscribed;
    }

    NEWSRC_UNLOCK(newsrc, lock);

    return ret;
}

void
camel_nntp_newsrc_subscribe_group (CamelNNTPNewsrc *newsrc, const char *group_name)
{
    NewsrcGroup *group;

    NEWSRC_LOCK(newsrc, lock);

    group = g_hash_table_lookup (newsrc->groups, group_name);

    if (group) {
        if (!group->subscribed)
            newsrc->dirty = TRUE;
        group->subscribed = TRUE;
    }
    else {
        camel_nntp_newsrc_group_add (newsrc, group_name, TRUE);
    }

    NEWSRC_UNLOCK(newsrc, lock);
}

void
camel_nntp_newsrc_unsubscribe_group (CamelNNTPNewsrc *newsrc, const char *group_name)
{
    NewsrcGroup *group;

    NEWSRC_LOCK(newsrc, lock);

    group = g_hash_table_lookup (newsrc->groups, group_name);
    if (group) {
        if (group->subscribed)
            newsrc->dirty = TRUE;
        group->subscribed = FALSE;
    }
    else {
        camel_nntp_newsrc_group_add (newsrc, group_name, FALSE);
    }

    NEWSRC_UNLOCK(newsrc, lock);
}

struct newsrc_ptr_array {
    GPtrArray *ptr_array;
    gboolean subscribed_only;
};

/* this needs to strdup the grup_name, if the group array is likely to change */
static void
get_group_foreach (char *group_name, NewsrcGroup *group, struct newsrc_ptr_array *npa)
{
    if (group->subscribed || !npa->subscribed_only) {
        g_ptr_array_add (npa->ptr_array, group_name);
    }
}

GPtrArray *
camel_nntp_newsrc_get_subscribed_group_names (CamelNNTPNewsrc *newsrc)
{
    struct newsrc_ptr_array npa;

    g_return_val_if_fail (newsrc, NULL);

    NEWSRC_LOCK(newsrc, lock);

    npa.ptr_array = g_ptr_array_new();
    npa.subscribed_only = TRUE;

    g_hash_table_foreach (newsrc->groups,
                  (GHFunc)get_group_foreach, &npa);

    NEWSRC_UNLOCK(newsrc, lock);

    return npa.ptr_array;
}

GPtrArray *
camel_nntp_newsrc_get_all_group_names (CamelNNTPNewsrc *newsrc)
{
    struct newsrc_ptr_array npa;

    g_return_val_if_fail (newsrc, NULL);

    NEWSRC_LOCK(newsrc, lock);

    npa.ptr_array = g_ptr_array_new();
    npa.subscribed_only = FALSE;

    g_hash_table_foreach (newsrc->groups,
                  (GHFunc)get_group_foreach, &npa);

    NEWSRC_UNLOCK(newsrc, lock);

    return npa.ptr_array;
}

void
camel_nntp_newsrc_free_group_names (CamelNNTPNewsrc *newsrc, GPtrArray *group_names)
{
    g_ptr_array_free (group_names, TRUE);
}

struct newsrc_fp {
    CamelNNTPNewsrc *newsrc;
    FILE *fp;
};

static void
camel_nntp_newsrc_write_group_line(gpointer key, NewsrcGroup *group, struct newsrc_fp *newsrc_fp)
{
    CamelNNTPNewsrc *newsrc;
    FILE *fp;
    int i;

    fp = newsrc_fp->fp;
    newsrc = newsrc_fp->newsrc;

    fprintf (fp, "%s%c", group->name, group->subscribed ? ':' : '!');

    if (group->ranges->len == 1
        && g_array_index (group->ranges, ArticleRange, 0).low == 0
        && g_array_index (group->ranges, ArticleRange, 0).high == 0) {
        fprintf (fp, "\n");

        return; /* special case since our parsing code will insert this
               bogus range if there were no read articles.  The code
               to add a range is smart enough to remove this one if we
               ever mark an article read, but we still need to deal with
               it if that code doesn't get hit. */
    }

    fprintf (fp, " ");

    for (i = 0; i < group->ranges->len; i ++) {
        char range_buffer[100];
        guint low = g_array_index (group->ranges, ArticleRange, i).low;
        guint high = g_array_index (group->ranges, ArticleRange, i).high;

        if (low == high)
            sprintf(range_buffer, "%d", low);
        else if (low == high - 1)
            sprintf(range_buffer, "%d,%d", low, high);
        else
            sprintf(range_buffer, "%d-%d", low, high);

        if (i != group->ranges->len - 1)
            strcat(range_buffer, ",");

        fprintf (fp, range_buffer);
    }

    fprintf (fp, "\n");
}

void 
camel_nntp_newsrc_write_to_file(CamelNNTPNewsrc *newsrc, FILE *fp)
{
    struct newsrc_fp newsrc_fp;

    g_return_if_fail (newsrc);

    newsrc_fp.newsrc = newsrc;
    newsrc_fp.fp = fp;

    NEWSRC_LOCK(newsrc, lock);

    g_hash_table_foreach (newsrc->groups,
                  (GHFunc)camel_nntp_newsrc_write_group_line,
                  &newsrc_fp);

    NEWSRC_UNLOCK(newsrc, lock);
}

void
camel_nntp_newsrc_write(CamelNNTPNewsrc *newsrc)
{
    FILE *fp;

    g_return_if_fail (newsrc);

    NEWSRC_LOCK(newsrc, lock);

    if (!newsrc->dirty) {
        NEWSRC_UNLOCK(newsrc, lock);
        return;
    }

    if ((fp = fopen(newsrc->filename, "w")) == NULL) {
        g_warning ("Couldn't open newsrc file '%s'.\n", newsrc->filename);
        NEWSRC_UNLOCK(newsrc, lock);
        return;
    }

    newsrc->dirty = FALSE;
    NEWSRC_UNLOCK(newsrc, lock);

    camel_nntp_newsrc_write_to_file(newsrc, fp);

    fclose(fp);
}

static void
camel_nntp_newsrc_parse_line(CamelNNTPNewsrc *newsrc, char *line)
{
    char *p, *comma, *dash;
    gboolean is_subscribed;
    NewsrcGroup *group;

    p = strchr(line, ':');

    if (p) {
        is_subscribed = TRUE;
    }
    else {
        p = strchr(line, '!');
        if (p)
            is_subscribed = FALSE;
        else
            return; /* bogus line. */
    }

    *p++ = '\0';

    group = camel_nntp_newsrc_group_add (newsrc, line, is_subscribed);

    do {
        guint high, low;

        comma = strchr(p, ',');

        if (comma)
            *comma = '\0';

        dash = strchr(p, '-');

        if (!dash) { /* there wasn't a dash.  must be just one number */
            high = low = atol(p);
        }
        else { /* there was a dash. */
            *dash = '\0';
            low = atol(p);
            *dash = '-';
            p = dash + 1;
            high = atol(p);
        }

        camel_nntp_newsrc_group_mark_range_read (newsrc, group, low, high);

        if (comma) {
            *comma = ',';
            p = comma + 1;
        }

    } while(comma);
}

static char*
get_line (char *buf, char **p)
{
    char *l;
    char *line;

    g_assert (*p == NULL || **p == '\n' || **p == '\0');

    if (*p == NULL) {
        *p = buf;

        if (**p == '\0')
            return NULL;
    }
    else {
        if (**p == '\0')
            return NULL;

        (*p) ++;
    
        /* if we just incremented to the end of the buffer, return NULL */
        if (**p == '\0')
            return NULL;
    }

    l = strchr (*p, '\n');
    if (l) {
        *l = '\0';
        line = g_strdup (*p);
        *l = '\n';
        *p = l;
    }
    else {
        /* we're at the last line (which isn't terminated by a \n, btw) */
        line = g_strdup (*p);
        (*p) += strlen (*p);
    }

    return line;
}

CamelNNTPNewsrc *
camel_nntp_newsrc_read_for_server (const char *server)
{
    int fd;
    char buf[1024];
    char *file_contents, *line, *p;
    char *filename;
    CamelNNTPNewsrc *newsrc;
    int newsrc_len;
    int len_read = 0;
    struct stat sb;

    filename = g_strdup_printf ("%s/.newsrc-%s", g_get_home_dir(), server);

    newsrc = g_new0(CamelNNTPNewsrc, 1);
    newsrc->filename = filename;
    newsrc->groups = g_hash_table_new (g_str_hash, g_str_equal);
    newsrc->lock = g_mutex_new();
    
    if ((fd = open(filename, O_RDONLY)) == -1) {
        g_warning ("~/.newsrc-%s not present.\n", server);
        return newsrc;
    }

    if (fstat (fd, &sb) == -1) {
        g_warning ("failed fstat on ~/.newsrc-%s: %s\n", server, strerror(errno));
        return newsrc;
    }
    newsrc_len = sb.st_size;

    file_contents = g_malloc (newsrc_len + 1);

    while (len_read < newsrc_len) {
        int c = read (fd, buf, sizeof (buf));

        if (c == -1)
            break;

        memcpy (&file_contents[len_read], buf, c);
        len_read += c;
    }
    file_contents [len_read] = 0;

    p = NULL;
    while ((line = get_line (file_contents, &p))) {
        camel_nntp_newsrc_parse_line(newsrc, line);
        g_free (line);
    }

    close (fd);
    g_free (file_contents);

    return newsrc;
}