1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
5  *
6  * - handling of info about user-deleted feed items
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26 
27 /* Global includes */
28 #include <glib.h>
29 #include <string.h>
30 
31 /* Claws Mail includes */
32 #include <codeconv.h>
33 #include <common/utils.h>
34 #include <file-utils.h>
35 
36 /* Local includes */
37 #include "rssyl.h"
38 #include "parse822.h"
39 #include "strutils.h"
40 
_new_deleted_item()41 static RDeletedItem *_new_deleted_item()
42 {
43 	RDeletedItem *ditem = g_new0(RDeletedItem, 1);
44 
45 	ditem->id = NULL;
46 	ditem->title = NULL;
47 	ditem->date_published = -1;
48 
49 	return ditem;
50 }
51 
_free_deleted_item(gpointer d,gpointer user_data)52 static void _free_deleted_item(gpointer d, gpointer user_data)
53 {
54 	RDeletedItem *ditem = (RDeletedItem *)d;
55 
56 	if (ditem == NULL)
57 		return;
58 
59 	g_free(ditem->id);
60 	g_free(ditem->title);
61 	g_free(ditem);
62 }
63 
rssyl_deleted_free(RFolderItem * ritem)64 void rssyl_deleted_free(RFolderItem *ritem)
65 {
66 	cm_return_if_fail(ritem != NULL);
67 
68 	if (ritem->deleted_items != NULL) {
69 		debug_print("RSSyl: releasing list of deleted items\n");
70 		g_slist_foreach(ritem->deleted_items, _free_deleted_item, NULL);
71 		g_slist_free(ritem->deleted_items);
72 		ritem->deleted_items = NULL;
73 	}
74 }
75 
_deleted_file_path(RFolderItem * ritem)76 static gchar * _deleted_file_path(RFolderItem *ritem)
77 {
78 	gchar *itempath, *deleted_file;
79 
80 	itempath = folder_item_get_path(&ritem->item);
81 	deleted_file = g_strconcat(itempath, G_DIR_SEPARATOR_S, RSSYL_DELETED_FILE, NULL);
82 	g_free(itempath);
83 
84 	return deleted_file;
85 }
86 
87 /***************************************************************/
rssyl_deleted_update(RFolderItem * ritem)88 void rssyl_deleted_update(RFolderItem *ritem)
89 {
90 	gchar *deleted_file, *contents, **lines, **line;
91 	GError *error = NULL;
92 	guint i = 0;
93 	RDeletedItem *ditem = NULL;
94 	GSList *deleted_items = NULL;
95 
96 	g_return_if_fail(ritem != NULL);
97 
98 	deleted_file = _deleted_file_path(ritem);
99 
100 	debug_print("RSSyl: (DELETED) getting list of deleted items from '%s'\n", deleted_file);
101 
102 	if (!g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
103 		debug_print("RSSyl: '%s' doesn't exist, ignoring\n", deleted_file);
104 		g_free(deleted_file);
105 		return;
106 	}
107 
108 	g_file_get_contents(deleted_file, &contents, NULL, &error);
109 
110 	if (error) {
111 		g_warning("GError: '%s'", error->message);
112 		g_error_free(error);
113 	}
114 
115 	if (contents != NULL) {
116 		lines = strsplit_no_copy(contents, '\n');
117 	} else {
118 		g_warning("Couldn't read '%s', ignoring", deleted_file);
119 		g_free(deleted_file);
120 		return;
121 	}
122 
123 	g_free(deleted_file);
124 
125 	while (lines[i]) {
126 		line = g_strsplit(lines[i], ": ", 2);
127 		if (line[0] && line[1] && strlen(line[0]) && strlen(line[1])) {
128 			if (!strcmp(line[0], "ID")) {
129 				ditem = _new_deleted_item();
130 				ditem->id = g_strdup(line[1]);
131 			} else if (ditem != NULL && !strcmp(line[0], "TITLE")) {
132 				ditem->title = g_strdup(line[1]);
133 			} else if (ditem != NULL && !strcmp(line[0], "DPUB")) {
134 				ditem->date_published = atoi(line[1]);
135 				deleted_items = g_slist_prepend(deleted_items, ditem);
136 				ditem = NULL;
137 			}
138 		}
139 
140 		g_strfreev(line);
141 		i++;
142 	}
143 
144 	g_free(lines);
145 	g_free(contents);
146 
147 	debug_print("RSSyl: got %d deleted items\n", g_slist_length(deleted_items));
148 
149 	rssyl_deleted_free(ritem);
150 	ritem->deleted_items = deleted_items;
151 }
152 
_store_one_deleted_item(gpointer data,gpointer user_data)153 static void _store_one_deleted_item(gpointer data, gpointer user_data)
154 {
155 	RDeletedItem *ditem = (RDeletedItem *)data;
156 	FILE *f = (FILE *)user_data;
157 	gboolean err = FALSE;
158 
159 	if (ditem == NULL || ditem->id == NULL)
160 		return;
161 
162 	err |= (fprintf(f,
163 			"ID: %s\n"
164 			"TITLE: %s\n"
165 			"DPUB: %lld\n",
166 			ditem->id, ditem->title,
167 			(long long)ditem->date_published) < 0);
168 
169 	if (err)
170 		debug_print("RSSyl: Error during writing deletion file.\n");
171 }
172 
rssyl_deleted_store_internal(GSList * deleted_items,const gchar * deleted_file)173 static void rssyl_deleted_store_internal(GSList *deleted_items, const gchar *deleted_file)
174 {
175 	FILE *f;
176 
177 	if (g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
178 		if (g_remove(deleted_file) != 0) {
179 			debug_print("RSSyl: Oops, couldn't delete '%s', bailing out\n",
180 					deleted_file);
181 			return;
182 		}
183 	}
184 
185 	if (g_slist_length(deleted_items) == 0)
186 		return;
187 
188 	if ((f = claws_fopen(deleted_file, "w")) == NULL) {
189 		debug_print("RSSyl: Couldn't open '%s', bailing out.\n", deleted_file);
190 		return;
191 	}
192 
193 	g_slist_foreach(deleted_items, (GFunc)_store_one_deleted_item,
194 			(gpointer)f);
195 
196 	claws_safe_fclose(f);
197 	debug_print("RSSyl: written and closed deletion file\n");
198 }
199 
rssyl_deleted_store(RFolderItem * ritem)200 void rssyl_deleted_store(RFolderItem *ritem)
201 {
202 	gchar *path;
203 
204 	g_return_if_fail(ritem != NULL);
205 
206 	path = _deleted_file_path(ritem);
207 	rssyl_deleted_store_internal(ritem->deleted_items, path);
208 	g_free(path);
209 }
210 
211 
212 /* Creates a FeedItem from a message file and uses the data to add a item
213  * to the list of deleted stuff. */
rssyl_deleted_add(RFolderItem * ritem,gchar * path)214 void rssyl_deleted_add(RFolderItem *ritem, gchar *path)
215 {
216 	FeedItem *fitem = NULL;
217 	RDeletedItem *ditem = NULL;
218 
219 	cm_return_if_fail(ritem != NULL);
220 	cm_return_if_fail(path != NULL);
221 
222 	debug_print("RSSyl: (DELETED) add\n");
223 
224 	if (!(fitem = rssyl_parse_folder_item_file(path)))
225 		return;
226 
227 	ditem = _new_deleted_item();
228 	ditem->id = g_strdup(feed_item_get_id(fitem));
229 	ditem->title = conv_unmime_header(feed_item_get_title(fitem),
230 			CS_UTF_8, FALSE);
231 	ditem->date_published = feed_item_get_date_published(fitem);
232 
233 	ritem->deleted_items = g_slist_prepend(ritem->deleted_items, ditem);
234 
235 	RFeedCtx *ctx = (RFeedCtx *)fitem->data;
236 	g_free(ctx->path);
237 	feed_item_free(fitem);
238 }
239 
_rssyl_deleted_check_func(gconstpointer a,gconstpointer b)240 static gint _rssyl_deleted_check_func(gconstpointer a, gconstpointer b)
241 {
242 	RDeletedItem *ditem = (RDeletedItem *)a;
243 	FeedItem *fitem = (FeedItem *)b;
244 	gchar *id;
245 	gboolean id_match = FALSE;
246 	gboolean title_match = FALSE;
247 	gboolean pubdate_match = FALSE;
248 
249 	g_return_val_if_fail(ditem != NULL, -10);
250 	g_return_val_if_fail(fitem != NULL, -20);
251 
252 	/* Following must match:
253 	 * ID, or if there is no ID, the URL, since that's
254 	 * what would have been stored in .deleted instead
255 	 * of ID... */
256 	if ((id = feed_item_get_id(fitem)) == NULL)
257 		id = feed_item_get_url(fitem);
258 
259 	if (ditem->id && id &&
260 			!strcmp(ditem->id, id))
261 		id_match = TRUE;
262 
263 	/* title, ... */
264 	if (ditem->title && feed_item_get_title(fitem) &&
265 			!strcmp(ditem->title, feed_item_get_title(fitem)))
266 		title_match = TRUE;
267 
268 	/* ...and time of publishing */
269 	if (ditem->date_published == -1 ||
270 			ditem->date_published == feed_item_get_date_published(fitem) ||
271 			ditem->date_published == feed_item_get_date_modified(fitem))
272 		pubdate_match = TRUE;
273 
274 	/* if all three match, it's the same item */
275 	if (id_match && title_match && pubdate_match) {
276 		return 0;
277 	}
278 
279 	/* not our item */
280 	return -1;
281 }
282 
283 /* Returns TRUE if fitem is found among the deleted stuff. */
rssyl_deleted_check(RFolderItem * ritem,FeedItem * fitem)284 gboolean rssyl_deleted_check(RFolderItem *ritem, FeedItem *fitem)
285 {
286 	cm_return_val_if_fail(ritem != NULL, FALSE);
287 	cm_return_val_if_fail(fitem != NULL, FALSE);
288 
289 	debug_print("RSSyl: (DELETED) check\n");
290 
291 	if (ritem->deleted_items == NULL)
292 		return FALSE;
293 
294 	if (g_slist_find_custom(ritem->deleted_items, (gconstpointer)fitem,
295 				_rssyl_deleted_check_func) != NULL)
296 		return TRUE;
297 
298 	return FALSE;
299 }
300 
301 /******** Expiring ********/
302 struct _RDelExpireCtx {
303 	RDeletedItem *ditem;
304 	gboolean delete;
305 };
306 
307 typedef struct _RDelExpireCtx RDelExpireCtx;
308 
_rssyl_deleted_expire_func_f(gpointer data,gpointer user_data)309 static void _rssyl_deleted_expire_func_f(gpointer data, gpointer user_data)
310 {
311 	FeedItem *fitem = (FeedItem *)data;
312 	RDelExpireCtx *ctx = (RDelExpireCtx *)user_data;
313 	gchar *id;
314 	gboolean id_match = FALSE;
315 	gboolean title_match = FALSE;
316 	gboolean pubdate_match = FALSE;
317 
318 	/* Following must match:
319 	 * ID, or if there is no ID, the URL, since that's
320 	 * what would have been stored in .deleted instead
321 	 * of ID... */
322 	if ((id = feed_item_get_id(fitem)) == NULL)
323 		id = feed_item_get_url(fitem);
324 
325 	if (ctx->ditem->id && id &&
326 			!strcmp(ctx->ditem->id, id))
327 		id_match = TRUE;
328 
329 	/* title, ... */
330 	if (ctx->ditem->title && feed_item_get_title(fitem) &&
331 			!strcmp(ctx->ditem->title, feed_item_get_title(fitem)))
332 		title_match = TRUE;
333 
334 	/* time of publishing, if set... */
335 	if (ctx->ditem->date_published == -1 ||
336 			ctx->ditem->date_published == feed_item_get_date_published(fitem) ||
337 			ctx->ditem->date_published == feed_item_get_date_modified(fitem))
338 		pubdate_match = TRUE;
339 
340 	/* if it's our item, set to NOT delete, since it's obviously
341 	 * still in the feed */
342 	if (id_match && title_match && pubdate_match)
343 		ctx->delete = FALSE;
344 }
345 
346 /* Checks each item in deleted items list against feed and removes it if
347  * it is not found there anymore. */
rssyl_deleted_expire(RFolderItem * ritem,Feed * feed)348 void rssyl_deleted_expire(RFolderItem *ritem, Feed *feed)
349 {
350 	GSList *d = NULL, *d2;
351 	RDelExpireCtx *ctx = NULL;
352 	RDeletedItem *ditem;
353 
354 	g_return_if_fail(ritem != NULL);
355 	g_return_if_fail(feed != NULL);
356 
357 	debug_print("RSSyl: (DELETED) expire\n");
358 
359 	/* Iterate over all items in the list */
360 	d = ritem->deleted_items;
361 	while (d) {
362 		ditem = (RDeletedItem *)d->data;
363 		ctx = g_new0(RDelExpireCtx, 1);
364 		ctx->ditem = ditem;
365 		ctx->delete = TRUE;
366 
367 		/* Adjust ctx->delete accordingly */
368 		feed_foreach_item(feed, _rssyl_deleted_expire_func_f, (gpointer)ctx);
369 
370 		/* Remove the item if necessary */
371 		if (ctx->delete) {
372 			debug_print("RSSyl: (DELETED) removing '%s' from list\n", ditem->title);
373 			d2 = d->next;
374 			ritem->deleted_items = g_slist_remove_link(ritem->deleted_items, d);
375 			_free_deleted_item(ditem, NULL);
376 			g_slist_free(d);
377 			d = d2;
378 		} else {
379 			d = d->next;
380 		}
381 
382 		g_free(ctx);
383 	}
384 }
385