/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-nntp-newsrc.c - .newsrc parsing/regurgitating code */ /* * * Copyright (C) 2000 Helix Code, Inc. * * 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 Place, Suite 330, Boston, MA 02111-1307 * USA */ #include #include #include #include #include #include #include #include #include "camel-nntp-newsrc.h" #include #ifdef ENABLE_THREADS #include #define NEWSRC_LOCK(f, l) (g_mutex_lock(((CamelNNTPNewsrc *)f)->l)) #define NEWSRC_UNLOCK(f, l) (g_mutex_unlock(((CamelNNTPNewsrc *)f)->l)) #else #define NEWSRC_LOCK(f, l) #define NEWSRC_UNLOCK(f, l) #endif typedef struct { guint low; guint high; } ArticleRange; typedef struct { char *name; GArray *ranges; gboolean subscribed; } NewsrcGroup; struct CamelNNTPNewsrc { gchar *filename; GHashTable *groups; gboolean dirty; #ifdef ENABLE_THREADS GMutex *lock; #endif } ; 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); ret = camel_nntp_newsrc_group_get_highest_article_read (newsrc, group); 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); ret = camel_nntp_newsrc_group_get_num_articles_read (newsrc, group); NEWSRC_UNLOCK(newsrc, lock); return ret; } void camel_nntp_newsrc_mark_article_read (CamelNNTPNewsrc *newsrc, const char *group_name, int num) { NEWSRC_LOCK(newsrc, lock); camel_nntp_newsrc_mark_range_read (newsrc, group_name, num, num); NEWSRC_UNLOCK(newsrc, lock); } 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); 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); 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) 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); #ifdef ENABLE_THREADS newsrc->lock = g_mutex_new(); #endif 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; }