1 /*
2  * Copyright (C) 2006 Andrej Kacian <andrej@kacian.sk>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 /* Global includes */
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 
28 /* Claws Mail includes */
29 #include <log.h>
30 #include <codeconv.h>
31 #include <main.h>
32 #include <procmsg.h>
33 
34 /* Local includes */
35 #include "libfeed/feed.h"
36 #include "libfeed/feeditem.h"
37 #include "libfeed/date.h"
38 #include "parse822.h"
39 #include "rssyl.h"
40 #include "rssyl_add_item.h"
41 #include "rssyl_deleted.h"
42 #include "rssyl_feed.h"
43 #include "rssyl_parse_feed.h"
44 #include "rssyl_prefs.h"
45 #include "strutils.h"
46 
rssyl_foreach_parse_func(gpointer data,gpointer user_data)47 static void rssyl_foreach_parse_func(gpointer data, gpointer user_data)
48 {
49 	FeedItem *feed_item = (FeedItem *)data;
50 	RFolderItem *ritem = (RFolderItem *)user_data;
51 
52 	rssyl_add_item(ritem, feed_item);
53 }
54 
55 struct _RSSylExpireItemsCtx {
56 	gboolean exists;
57 	FeedItem *item;
58 	GSList *expired_ids;
59 };
60 
61 typedef struct _RSSylExpireItemsCtx RSSylExpireItemsCtx;
62 
expire_items_func(gpointer data,gpointer user_data)63 static void expire_items_func(gpointer data, gpointer user_data)
64 {
65 	RSSylExpireItemsCtx *ctx = (RSSylExpireItemsCtx *)user_data;
66 	FeedItem *item = (FeedItem *)data;
67 	gchar *id = NULL, *id2 = NULL;
68 
69 	if( (id = feed_item_get_id(item)) == NULL )
70 		id = feed_item_get_url(item);
71 
72 	if( id == NULL )
73 		return;
74 
75 	if( (id2 = feed_item_get_id(ctx->item)) == NULL )
76 		id2 = feed_item_get_url(ctx->item);
77 
78 	if( id2 == NULL )
79 		return;
80 
81 	/* Simply check ID, as we should have up-to-date items right now. */
82 	if( !strcmp(id, id2) )
83 		ctx->exists = TRUE;
84 }
85 
rssyl_expire_items(RFolderItem * ritem,Feed * feed)86 static void rssyl_expire_items(RFolderItem *ritem, Feed *feed)
87 {
88 	FeedItem *item = NULL;
89 	GSList *i = NULL;
90 	RSSylExpireItemsCtx *ctx = NULL;
91 	RFeedCtx *fctx;
92 
93 	debug_print("RSSyl: rssyl_expire_items()\n");
94 
95 	g_return_if_fail(ritem != NULL);
96 	g_return_if_fail(ritem->items != NULL);
97 	g_return_if_fail(feed != NULL);
98 
99 	ctx = malloc( sizeof(RSSylExpireItemsCtx) );
100 	ctx->expired_ids = NULL;
101 
102 	/* Check each locally stored item, if it is still in the upstream
103 	 * feed - xnay it if not. */
104 	for( i = ritem->items; i != NULL; i = i->next ) {
105 		item = (FeedItem *)i->data;
106 
107 		/* Comments will be expired later, once we know which parent items
108 		 * have been expired. */
109 		if (feed_item_get_parent_id(item) != NULL)
110 			continue;
111 
112 		/* Find matching item in the fresh feed. */
113 		ctx->exists = FALSE;
114 		ctx->item = item;
115 		feed_foreach_item(feed, expire_items_func, ctx);
116 
117 		if( !ctx->exists ) {
118 			/* No match, add item ids to the list and get rid of it. */
119 			debug_print("RSSyl: expiring '%s'\n", feed_item_get_id(item));
120 			ctx->expired_ids = g_slist_prepend(ctx->expired_ids,
121 					g_strdup(feed_item_get_id(item)));
122 			fctx = (RFeedCtx *)item->data;
123 			if (g_remove(fctx->path) != 0) {
124 				debug_print("RSSyl: couldn't delete expiring item file '%s'\n",
125 						fctx->path);
126 			}
127 		}
128 	}
129 
130 	/* Now do one more pass over folder contents, and expire comments
131 	 * whose parents are gone. */
132 	for( i = ritem->items; i != NULL; i = i->next ) {
133 		item = (FeedItem *)i->data;
134 
135 		/* Handle comments expiration. */
136 		if (feed_item_get_parent_id(item) != NULL) {
137 			/* If its parent's id is on list of expired ids, this comment
138 			 * can go as well. */
139 			if (g_slist_find_custom(ctx->expired_ids,
140 					feed_item_get_parent_id(item), (GCompareFunc)g_strcmp0)) {
141 				debug_print("RSSyl: expiring comment '%s'\n", feed_item_get_id(item));
142 				fctx = (RFeedCtx *)item->data;
143 				if (g_remove(fctx->path) != 0) {
144 					debug_print("RSSyl: couldn't delete expiring comment file '%s'\n",
145 							fctx->path);
146 				}
147 			}
148 		}
149 	}
150 
151 	debug_print("RSSyl: expired %d items\n", g_slist_length(ctx->expired_ids));
152 
153 	slist_free_strings_full(ctx->expired_ids);
154 	g_free(ctx);
155 }
156 
157 /* -------------------------------------------------------------------------
158  * rssyl_parse_feed() */
159 
rssyl_parse_feed(RFolderItem * ritem,Feed * feed)160 gboolean rssyl_parse_feed(RFolderItem *ritem, Feed *feed)
161 {
162 	gchar *tmp = NULL, *tmp2 = NULL;
163 	gint i = 1;
164 
165 	g_return_val_if_fail(ritem != NULL, FALSE);
166 	g_return_val_if_fail(feed != NULL, FALSE);
167 	g_return_val_if_fail(feed->title != NULL, FALSE);
168 
169 	debug_print("RSSyl: parse_feed\n");
170 
171 	/* Set the last_update timestamp here, so it is the same for all items */
172 	ritem->last_update = time(NULL);
173 
174 	/* If the upstream feed changed its title, change name of our folder
175 	 * accordingly even if user has renamed it before. This makes sure that
176 	 * user will be aware of the upstream title change. */
177 	if( !ritem->ignore_title_rename &&
178 			(ritem->official_title == NULL ||
179 			strcmp(feed->title, ritem->official_title)) ) {
180 		g_free(ritem->official_title);
181 		ritem->official_title = g_strdup(feed->title);
182 
183 		tmp = rssyl_format_string(feed->title, TRUE, TRUE);
184 
185 		tmp2 = g_strdup(tmp);
186 		while (folder_item_rename(&ritem->item, tmp2) != 0 && i < 20) {
187 			g_free(tmp2);
188 			tmp2 = g_strdup_printf("%s__%d", tmp, ++i);
189 			debug_print("RSSyl: couldn't rename, trying '%s'\n", tmp2);
190 		}
191 		/* TODO: handle case when i reaches 20 */
192 
193 		g_free(tmp);
194 		g_free(tmp2);
195 
196 		/* FIXME: update name in properties */
197 		/* FIXME: store feed properties */
198 	}
199 
200 	folder_item_update_freeze();
201 
202 	/* Read contents of folder, so we can check for duplicates/updates */
203 	rssyl_folder_read_existing(ritem);
204 
205 	if( claws_is_exiting() ) {
206 		debug_print("RSSyl: Claws Mail is exiting, bailing out\n");
207 		log_print(LOG_PROTOCOL, RSSYL_LOG_ABORTED_EXITING, ritem->url);
208 		folder_item_update_thaw();
209 		return TRUE;
210 	}
211 
212 	/* Parse each item in the feed, adding or updating existing items if
213 	 * necessary */
214 	if( feed_n_items(feed) > 0 )
215 		feed_foreach_item(feed, rssyl_foreach_parse_func, (gpointer)ritem);
216 
217 	if( !ritem->keep_old && !ritem->fetching_comments ) {
218 		rssyl_folder_read_existing(ritem);
219 		rssyl_expire_items(ritem, feed);
220 	}
221 
222 	folder_item_scan(&ritem->item);
223 	folder_item_update_thaw();
224 
225 	if( !ritem->fetching_comments )
226 		log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATED, ritem->url);
227 
228 	return TRUE;
229 }
230