Rename claws_io to file-utils, and move file-related functions
[claws.git] / src / plugins / rssyl / rssyl_deleted.c
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
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
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
64 void rssyl_deleted_free(GSList *deleted_items)
65 {
66         if (deleted_items != NULL) {
67                 debug_print("RSSyl: releasing list of deleted items\n");
68                 g_slist_foreach(deleted_items, _free_deleted_item, NULL);
69                 g_slist_free(deleted_items);
70                 deleted_items = NULL;
71         }
72 }
73
74 static gchar * _deleted_file_path(RFolderItem *ritem)
75 {
76         gchar *itempath, *deleted_file;
77
78         itempath = folder_item_get_path(&ritem->item);
79         deleted_file = g_strconcat(itempath, G_DIR_SEPARATOR_S, RSSYL_DELETED_FILE, NULL);
80         g_free(itempath);
81
82         return deleted_file;
83 }
84
85 /***************************************************************/
86 GSList *rssyl_deleted_update(RFolderItem *ritem)
87 {
88         gchar *deleted_file, *contents, **lines, **line;
89         GError *error = NULL;
90         guint i = 0;
91         RDeletedItem *ditem = NULL;
92         GSList *deleted_items = NULL;
93
94         g_return_val_if_fail(ritem != NULL, NULL);
95
96         deleted_file = _deleted_file_path(ritem);
97
98         debug_print("RSSyl: getting list of deleted items from '%s'\n", deleted_file);
99
100         if (!g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
101                 debug_print("RSSyl: '%s' doesn't exist, ignoring\n", deleted_file);
102                 g_free(deleted_file);
103                 return NULL;
104         }
105
106         g_file_get_contents(deleted_file, &contents, NULL, &error);
107
108         if (error) {
109                 g_warning("GError: '%s'", error->message);
110                 g_error_free(error);
111         }
112
113         if (contents != NULL) {
114                 lines = strsplit_no_copy(contents, '\n');
115         } else {
116                 g_warning("Couldn't read '%s', ignoring", deleted_file);
117                 g_free(deleted_file);
118                 return NULL;
119         }
120
121         g_free(deleted_file);
122
123         while (lines[i]) {
124                 line = g_strsplit(lines[i], ": ", 2);
125                 if (line[0] && line[1] && strlen(line[0]) && strlen(line[1])) {
126                         if (!strcmp(line[0], "ID")) {
127                                 ditem = _new_deleted_item();
128                                 ditem->id = g_strdup(line[1]);
129                         } else if (ditem != NULL && !strcmp(line[0], "TITLE")) {
130                                 ditem->title = g_strdup(line[1]);
131                         } else if (ditem != NULL && !strcmp(line[0], "DPUB")) {
132                                 ditem->date_published = atoi(line[1]);
133                                 deleted_items = g_slist_prepend(deleted_items, ditem);
134                                 ditem = NULL;
135                         }
136                 }
137
138                 g_strfreev(line);
139                 i++;
140         }
141
142         g_free(lines);
143         g_free(contents);
144
145         debug_print("RSSyl: got %d deleted items\n", g_slist_length(deleted_items));
146         return deleted_items;
147 }
148
149 static void _store_one_deleted_item(gpointer data, gpointer user_data)
150 {
151         RDeletedItem *ditem = (RDeletedItem *)data;
152         FILE *f = (FILE *)user_data;
153         gboolean err = FALSE;
154
155         if (ditem == NULL || ditem->id == NULL)
156                 return;
157
158         err |= (fprintf(f,
159                         "ID: %s\n"
160                         "TITLE: %s\n"
161                         "DPUB: %lld\n",
162                         ditem->id, ditem->title,
163                         (long long)ditem->date_published) < 0);
164
165         if (err)
166                 debug_print("RSSyl: Error during writing deletion file.\n");
167 }
168
169 static void rssyl_deleted_store_internal(GSList *deleted_items, const gchar *deleted_file)
170 {
171         FILE *f;
172
173         if (g_file_test(deleted_file, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
174                 if (g_remove(deleted_file) != 0) {
175                         debug_print("RSSyl: Oops, couldn't delete '%s', bailing out\n",
176                                         deleted_file);
177                         return;
178                 }
179         }
180
181         if (g_slist_length(deleted_items) == 0)
182                 return;
183
184         if ((f = claws_fopen(deleted_file, "w")) == NULL) {
185                 debug_print("RSSyl: Couldn't open '%s', bailing out.\n", deleted_file);
186                 return;
187         }
188
189         g_slist_foreach(deleted_items, (GFunc)_store_one_deleted_item,
190                         (gpointer)f);
191
192         claws_safe_fclose(f);
193         debug_print("RSSyl: written and closed deletion file\n");
194 }
195
196 void rssyl_deleted_store(RFolderItem *ritem)
197 {
198         gchar *path;
199
200         g_return_if_fail(ritem != NULL);
201
202         path = _deleted_file_path(ritem);
203         rssyl_deleted_store_internal(ritem->deleted_items, path);
204         g_free(path);
205 }
206
207
208 /* Creates a FeedItem from a message file and uses the data to add a item
209  * to the list of deleted stuff. */
210 void rssyl_deleted_add(RFolderItem *ritem, gchar *path)
211 {
212         FeedItem *fitem = NULL;
213         RDeletedItem *ditem = NULL;
214         GSList *deleted_items = rssyl_deleted_update(ritem);
215         gchar *deleted_file = NULL;
216
217         if (!(fitem = rssyl_parse_folder_item_file(path)))
218                 return;
219
220         ditem = _new_deleted_item();
221         ditem->id = g_strdup(feed_item_get_id(fitem));
222         ditem->title = conv_unmime_header(feed_item_get_title(fitem),
223                         CS_UTF_8, FALSE);
224         ditem->date_published = feed_item_get_date_published(fitem);
225
226         deleted_items = g_slist_prepend(deleted_items, ditem);
227
228         deleted_file = _deleted_file_path(ritem);
229         rssyl_deleted_store_internal(deleted_items, deleted_file);
230         g_free(deleted_file);
231
232         rssyl_deleted_free(deleted_items);
233
234         RFeedCtx *ctx = (RFeedCtx *)fitem->data;
235         g_free(ctx->path);
236         feed_item_free(fitem);
237 }
238
239 static gint _rssyl_deleted_check_func(gconstpointer a, gconstpointer b)
240 {
241         RDeletedItem *ditem = (RDeletedItem *)a;
242         FeedItem *fitem = (FeedItem *)b;
243         gchar *id;
244         gboolean id_match = FALSE;
245         gboolean title_match = FALSE;
246         gboolean pubdate_match = FALSE;
247
248         g_return_val_if_fail(ditem != NULL, -10);
249         g_return_val_if_fail(fitem != NULL, -20);
250
251         /* Following must match:
252          * ID, or if there is no ID, the URL, since that's
253          * what would have been stored in .deleted instead
254          * of ID... */
255         if ((id = feed_item_get_id(fitem)) == NULL)
256                 id = feed_item_get_url(fitem);
257
258         if (ditem->id && id &&
259                         !strcmp(ditem->id, id))
260                 id_match = TRUE;
261
262         /* title, ... */
263         if (ditem->title && feed_item_get_title(fitem) &&
264                         !strcmp(ditem->title, feed_item_get_title(fitem)))
265                 title_match = TRUE;
266
267         /* ...and time of publishing */
268         if (ditem->date_published == -1 ||
269                         ditem->date_published == feed_item_get_date_published(fitem))
270                 pubdate_match = TRUE;
271
272         /* if all three match, it's the same item */
273         if (id_match && title_match && pubdate_match) {
274                 return 0;
275         }
276
277         /* not our item */
278         return -1;
279 }
280
281 /* Returns TRUE if fitem is found among the deleted stuff. */
282 gboolean rssyl_deleted_check(GSList *deleted_items, FeedItem *fitem)
283 {
284         if (g_slist_find_custom(deleted_items, (gconstpointer)fitem,
285                                 _rssyl_deleted_check_func) != NULL)
286                 return TRUE;
287
288         return FALSE;
289 }
290
291 /******** Expiring ********/
292 struct _RDelExpireCtx {
293         RDeletedItem *ditem;
294         gboolean delete;
295 };
296
297 typedef struct _RDelExpireCtx RDelExpireCtx;
298
299 static void _rssyl_deleted_expire_func_f(gpointer data, gpointer user_data)
300 {
301         FeedItem *fitem = (FeedItem *)data;
302         RDelExpireCtx *ctx = (RDelExpireCtx *)user_data;
303         gchar *id;
304         gboolean id_match = FALSE;
305         gboolean title_match = FALSE;
306         gboolean pubdate_match = FALSE;
307
308         /* Following must match:
309          * ID, or if there is no ID, the URL, since that's
310          * what would have been stored in .deleted instead
311          * of ID... */
312         if ((id = feed_item_get_id(fitem)) == NULL)
313                 id = feed_item_get_url(fitem);
314
315         if (ctx->ditem->id && id &&
316                         !strcmp(ctx->ditem->id, id))
317                 id_match = TRUE;
318
319         /* title, ... */
320         if (ctx->ditem->title && feed_item_get_title(fitem) &&
321                         !strcmp(ctx->ditem->title, feed_item_get_title(fitem)))
322                 title_match = TRUE;
323
324         /* time of publishing, if set... */
325         if (ctx->ditem->date_published == -1 ||
326                         ctx->ditem->date_published == feed_item_get_date_published(fitem))
327                 pubdate_match = TRUE;
328
329         /* if it's our item, set to NOT delete, since it's obviously
330          * still in the feed */
331         if (id_match && title_match && pubdate_match)
332                 ctx->delete = FALSE;
333 }
334
335 /* Checks each item in deleted items list against feed and removes it if
336  * it is not found there anymore. */
337 void rssyl_deleted_expire(RFolderItem *ritem, Feed *feed)
338 {
339         GSList *d = NULL, *d2;
340         RDelExpireCtx *ctx = NULL;
341         RDeletedItem *ditem;
342
343         g_return_if_fail(ritem != NULL);
344         g_return_if_fail(feed != NULL);
345
346         ritem->deleted_items = rssyl_deleted_update(ritem);
347
348         /* Iterate over all items in the list */
349         d = ritem->deleted_items;
350         while (d) {
351                 ditem = (RDeletedItem *)d->data;
352                 ctx = g_new0(RDelExpireCtx, 1);
353                 ctx->ditem = ditem;
354                 ctx->delete = TRUE;
355
356                 /* Adjust ctx->delete accordingly */
357                 feed_foreach_item(feed, _rssyl_deleted_expire_func_f, (gpointer)ctx);
358
359                 /* Remove the item if necessary */
360                 if (ctx->delete) {
361                         debug_print("RSSyl: (DELETED) removing '%s' from list\n", ditem->title);
362                         d2 = d->next;
363                         ritem->deleted_items = g_slist_remove_link(ritem->deleted_items, d);
364                         d = d2;
365                         continue;
366                 } else {
367                         d = d->next;
368                 }
369
370                 g_free(ctx);
371         }
372
373         /* Write the new list to disk */
374         rssyl_deleted_store(ritem);
375
376         /* And clean up after myself */
377         rssyl_deleted_free(ritem->deleted_items);
378 }