1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Chris Toshok <toshok@ximian.com>
5 * Copyright 2000-2003 Ximian, Inc. (www.ximian.com)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
36 #include <glib/gstdio.h>
38 #include "camel-folder-summary.h"
40 #include "camel-nntp-newsrc.h"
42 #define NEWSRC_LOCK(f, l) (g_mutex_lock(((CamelNNTPNewsrc *)f)->l))
43 #define NEWSRC_UNLOCK(f, l) (g_mutex_unlock(((CamelNNTPNewsrc *)f)->l))
56 struct CamelNNTPNewsrc {
65 camel_nntp_newsrc_group_add (CamelNNTPNewsrc *newsrc, const char *group_name, gboolean subscribed)
67 NewsrcGroup *new_group = g_malloc(sizeof(NewsrcGroup));
69 new_group->name = g_strdup(group_name);
70 new_group->subscribed = subscribed;
71 new_group->ranges = g_array_new (FALSE, FALSE, sizeof (ArticleRange));
73 g_hash_table_insert (newsrc->groups, new_group->name, new_group);
81 camel_nntp_newsrc_group_get_highest_article_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group)
83 if (!group || group->ranges->len == 0)
86 return g_array_index(group->ranges, ArticleRange, group->ranges->len - 1).high;
90 camel_nntp_newsrc_group_get_num_articles_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group)
98 for (i = 0; i < group->ranges->len; i ++)
99 count += (g_array_index(group->ranges, ArticleRange, i).high -
100 g_array_index(group->ranges, ArticleRange, i).low) + 1;
107 camel_nntp_newsrc_group_mark_range_read(CamelNNTPNewsrc *newsrc, NewsrcGroup *group, long low, long high)
111 if (group->ranges->len == 1
112 && g_array_index (group->ranges, ArticleRange, 0).low == 0
113 && g_array_index (group->ranges, ArticleRange, 0).high == 0) {
114 g_array_index (group->ranges, ArticleRange, 0).low = low;
115 g_array_index (group->ranges, ArticleRange, 0).high = high;
117 newsrc->dirty = TRUE;
120 ArticleRange tmp_range;
122 for (i = 0; i < group->ranges->len; i ++) {
123 guint range_low = g_array_index (group->ranges, ArticleRange, i).low;
124 guint range_high = g_array_index (group->ranges, ArticleRange, i).high;
126 /* if it's already part of a range, return immediately. */
127 if (low >= range_low &&
130 high <= range_high) {
133 /* if we have a new lower bound for this range, set it. */
134 else if (low <= range_low
136 && high <= range_high) {
137 g_array_index (group->ranges, ArticleRange, i).low = low;
138 newsrc->dirty = TRUE;
141 /* if we have a new upper bound for this range, set it. */
142 else if (high >= range_high
144 && low <= range_high) {
145 g_array_index (group->ranges, ArticleRange, i).high = high;
146 newsrc->dirty = TRUE;
149 /* if we would be inserting another range that
150 starts one index higher than an existing
151 one, make the upper value of the existing
152 range the upper value of the new one. */
153 else if (low == range_high + 1) {
154 g_array_index (group->ranges, ArticleRange, i).high = high;
155 newsrc->dirty = TRUE;
158 /* if we would be inserting another range that
159 ends one index lower than an existing one,
160 group the existing range by setting its low
162 else if (high == range_low - 1) {
163 g_array_index (group->ranges, ArticleRange, i).low = low;
164 newsrc->dirty = TRUE;
167 /* if the range lies entirely outside another
168 range, doesn't coincide with it's
169 endpoints, and has lower values, insert it
170 into the middle of the list. */
171 else if (low < range_low
172 && high < range_low) {
174 tmp_range.high = high;
176 group->ranges = g_array_insert_val (group->ranges, i, tmp_range);
177 newsrc->dirty = TRUE;
183 /* if we made it here, the range needs to go at the end */
185 tmp_range.high = high;
186 group->ranges = g_array_append_val (group->ranges, tmp_range);
187 newsrc->dirty = TRUE;
192 camel_nntp_newsrc_get_highest_article_read (CamelNNTPNewsrc *newsrc, const char *group_name)
197 NEWSRC_LOCK(newsrc, lock);
199 group = g_hash_table_lookup (newsrc->groups, group_name);
202 ret = camel_nntp_newsrc_group_get_highest_article_read (newsrc, group);
206 NEWSRC_UNLOCK(newsrc, lock);
212 camel_nntp_newsrc_get_num_articles_read (CamelNNTPNewsrc *newsrc, const char *group_name)
217 NEWSRC_LOCK(newsrc, lock);
219 group = g_hash_table_lookup (newsrc->groups, group_name);
222 ret = camel_nntp_newsrc_group_get_num_articles_read (newsrc, group);
226 NEWSRC_UNLOCK(newsrc, lock);
232 camel_nntp_newsrc_mark_article_read (CamelNNTPNewsrc *newsrc, const char *group_name, int num)
234 camel_nntp_newsrc_mark_range_read (newsrc, group_name, num, num);
238 camel_nntp_newsrc_mark_range_read(CamelNNTPNewsrc *newsrc, const char *group_name, long low, long high)
242 /* swap them if they're in the wrong order. */
251 NEWSRC_LOCK(newsrc, lock);
252 group = g_hash_table_lookup (newsrc->groups, group_name);
255 camel_nntp_newsrc_group_mark_range_read (newsrc, group, low, high);
256 NEWSRC_UNLOCK(newsrc, lock);
260 camel_nntp_newsrc_article_is_read (CamelNNTPNewsrc *newsrc, const char *group_name, long num)
266 NEWSRC_LOCK(newsrc, lock);
267 group = g_hash_table_lookup (newsrc->groups, group_name);
270 for (i = 0; i < group->ranges->len; i++) {
271 if (num >= g_array_index (group->ranges, ArticleRange, i).low &&
272 num <= g_array_index (group->ranges, ArticleRange, i).high) {
279 NEWSRC_UNLOCK(newsrc, lock);
285 camel_nntp_newsrc_group_is_subscribed (CamelNNTPNewsrc *newsrc, const char *group_name)
290 NEWSRC_LOCK(newsrc, lock);
292 group = g_hash_table_lookup (newsrc->groups, group_name);
295 ret = group->subscribed;
298 NEWSRC_UNLOCK(newsrc, lock);
304 camel_nntp_newsrc_subscribe_group (CamelNNTPNewsrc *newsrc, const char *group_name)
308 NEWSRC_LOCK(newsrc, lock);
310 group = g_hash_table_lookup (newsrc->groups, group_name);
313 if (!group->subscribed)
314 newsrc->dirty = TRUE;
315 group->subscribed = TRUE;
318 camel_nntp_newsrc_group_add (newsrc, group_name, TRUE);
321 NEWSRC_UNLOCK(newsrc, lock);
325 camel_nntp_newsrc_unsubscribe_group (CamelNNTPNewsrc *newsrc, const char *group_name)
329 NEWSRC_LOCK(newsrc, lock);
331 group = g_hash_table_lookup (newsrc->groups, group_name);
333 if (group->subscribed)
334 newsrc->dirty = TRUE;
335 group->subscribed = FALSE;
338 camel_nntp_newsrc_group_add (newsrc, group_name, FALSE);
341 NEWSRC_UNLOCK(newsrc, lock);
344 struct newsrc_ptr_array {
345 GPtrArray *ptr_array;
346 gboolean subscribed_only;
349 /* this needs to strdup the grup_name, if the group array is likely to change */
351 get_group_foreach (char *group_name, NewsrcGroup *group, struct newsrc_ptr_array *npa)
353 if (group->subscribed || !npa->subscribed_only) {
354 g_ptr_array_add (npa->ptr_array, group_name);
359 camel_nntp_newsrc_get_subscribed_group_names (CamelNNTPNewsrc *newsrc)
361 struct newsrc_ptr_array npa;
363 g_return_val_if_fail (newsrc, NULL);
365 NEWSRC_LOCK(newsrc, lock);
367 npa.ptr_array = g_ptr_array_new();
368 npa.subscribed_only = TRUE;
370 g_hash_table_foreach (newsrc->groups,
371 (GHFunc)get_group_foreach, &npa);
373 NEWSRC_UNLOCK(newsrc, lock);
375 return npa.ptr_array;
379 camel_nntp_newsrc_get_all_group_names (CamelNNTPNewsrc *newsrc)
381 struct newsrc_ptr_array npa;
383 g_return_val_if_fail (newsrc, NULL);
385 NEWSRC_LOCK(newsrc, lock);
387 npa.ptr_array = g_ptr_array_new();
388 npa.subscribed_only = FALSE;
390 g_hash_table_foreach (newsrc->groups,
391 (GHFunc)get_group_foreach, &npa);
393 NEWSRC_UNLOCK(newsrc, lock);
395 return npa.ptr_array;
399 camel_nntp_newsrc_free_group_names (CamelNNTPNewsrc *newsrc, GPtrArray *group_names)
401 g_ptr_array_free (group_names, TRUE);
405 CamelNNTPNewsrc *newsrc;
410 camel_nntp_newsrc_write_group_line(gpointer key, NewsrcGroup *group, struct newsrc_fp *newsrc_fp)
412 CamelNNTPNewsrc *newsrc;
417 newsrc = newsrc_fp->newsrc;
419 fprintf (fp, "%s%c", group->name, group->subscribed ? ':' : '!');
421 if (group->ranges->len == 1
422 && g_array_index (group->ranges, ArticleRange, 0).low == 0
423 && g_array_index (group->ranges, ArticleRange, 0).high == 0) {
426 return; /* special case since our parsing code will insert this
427 bogus range if there were no read articles. The code
428 to add a range is smart enough to remove this one if we
429 ever mark an article read, but we still need to deal with
430 it if that code doesn't get hit. */
435 for (i = 0; i < group->ranges->len; i ++) {
436 char range_buffer[100];
437 guint low = g_array_index (group->ranges, ArticleRange, i).low;
438 guint high = g_array_index (group->ranges, ArticleRange, i).high;
441 sprintf(range_buffer, "%d", low);
442 else if (low == high - 1)
443 sprintf(range_buffer, "%d,%d", low, high);
445 sprintf(range_buffer, "%d-%d", low, high);
447 if (i != group->ranges->len - 1)
448 strcat(range_buffer, ",");
450 fprintf (fp, range_buffer);
457 camel_nntp_newsrc_write_to_file(CamelNNTPNewsrc *newsrc, FILE *fp)
459 struct newsrc_fp newsrc_fp;
461 g_return_if_fail (newsrc);
463 newsrc_fp.newsrc = newsrc;
466 NEWSRC_LOCK(newsrc, lock);
468 g_hash_table_foreach (newsrc->groups,
469 (GHFunc)camel_nntp_newsrc_write_group_line,
472 NEWSRC_UNLOCK(newsrc, lock);
476 camel_nntp_newsrc_write(CamelNNTPNewsrc *newsrc)
480 g_return_if_fail (newsrc);
482 NEWSRC_LOCK(newsrc, lock);
484 if (!newsrc->dirty) {
485 NEWSRC_UNLOCK(newsrc, lock);
489 if ((fp = g_fopen(newsrc->filename, "w")) == NULL) {
490 g_warning ("Couldn't open newsrc file '%s'.\n", newsrc->filename);
491 NEWSRC_UNLOCK(newsrc, lock);
495 newsrc->dirty = FALSE;
496 NEWSRC_UNLOCK(newsrc, lock);
498 camel_nntp_newsrc_write_to_file(newsrc, fp);
504 camel_nntp_newsrc_parse_line(CamelNNTPNewsrc *newsrc, char *line)
506 char *p, *comma, *dash;
507 gboolean is_subscribed;
510 p = strchr(line, ':');
513 is_subscribed = TRUE;
516 p = strchr(line, '!');
518 is_subscribed = FALSE;
520 return; /* bogus line. */
525 group = camel_nntp_newsrc_group_add (newsrc, line, is_subscribed);
530 comma = strchr(p, ',');
535 dash = strchr(p, '-');
537 if (!dash) { /* there wasn't a dash. must be just one number */
538 high = low = atol(p);
540 else { /* there was a dash. */
548 camel_nntp_newsrc_group_mark_range_read (newsrc, group, low, high);
559 get_line (char *buf, char **p)
564 g_assert (*p == NULL || **p == '\n' || **p == '\0');
578 /* if we just incremented to the end of the buffer, return NULL */
583 l = strchr (*p, '\n');
586 line = g_strdup (*p);
591 /* we're at the last line (which isn't terminated by a \n, btw) */
592 line = g_strdup (*p);
600 camel_nntp_newsrc_read_for_server (const char *server)
604 char *file_contents, *line, *p;
606 CamelNNTPNewsrc *newsrc;
611 filename = g_strdup_printf ("%s/.newsrc-%s", g_get_home_dir(), server);
613 newsrc = g_new0(CamelNNTPNewsrc, 1);
614 newsrc->filename = filename;
615 newsrc->groups = g_hash_table_new (g_str_hash, g_str_equal);
616 newsrc->lock = g_mutex_new();
618 if ((fd = g_open(filename, O_RDONLY, 0)) == -1) {
619 g_warning ("~/.newsrc-%s not present.\n", server);
623 if (fstat (fd, &sb) == -1) {
624 g_warning ("failed fstat on ~/.newsrc-%s: %s\n", server, strerror(errno));
628 newsrc_len = sb.st_size;
630 file_contents = g_malloc (newsrc_len + 1);
632 while (len_read < newsrc_len) {
633 int c = read (fd, buf, sizeof (buf));
638 memcpy (&file_contents[len_read], buf, c);
641 file_contents [len_read] = 0;
644 while ((line = get_line (file_contents, &p))) {
645 camel_nntp_newsrc_parse_line(newsrc, line);
650 g_free (file_contents);