update copyright year
[claws.git] / src / plugins / rssyl / rssyl.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  * - s-c folderclass callback handler functions
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 <glib/gi18n.h>
30 #include <curl/curl.h>
31
32 /* Claws Mail includes */
33 #include <folder.h>
34 #include <procmsg.h>
35 #include <localfolder.h>
36 #include <main.h>
37 #include <mh.h>
38 #include <xml.h>
39 #include <toolbar.h>
40 #include <prefs_common.h>
41 #include <prefs_toolbar.h>
42 #include <utils.h>
43 #include <file-utils.h>
44
45 /* Local includes */
46 #include "libfeed/feeditem.h"
47 #include "rssyl.h"
48 #include "rssyl_deleted.h"
49 #include "rssyl_gtk.h"
50 #include "rssyl_feed.h"
51 #include "rssyl_prefs.h"
52 #include "rssyl_subscribe.h"
53 #include "rssyl_update_feed.h"
54 #include "rssyl_update_format.h"
55 #include "opml_import.h"
56 #include "opml_export.h"
57 #include "strutils.h"
58
59 FolderClass rssyl_class;
60
61 static gint rssyl_create_tree(Folder *folder);
62 static gint rssyl_scan_tree(Folder *folder);
63
64 static gboolean existing_tree_found = FALSE;
65
66 static void rssyl_init_read_func(FolderItem *item, gpointer data)
67 {
68         RFolderItem *ritem = (RFolderItem *)item;
69         RPrefs *rsprefs = NULL;
70
71         if( !IS_RSSYL_FOLDER_ITEM(item) )
72                 return;
73
74         existing_tree_found = TRUE;
75
76         /* Don't do anything if we're on root of our folder tree or on
77          * a regular folder (no feed) */
78         if( folder_item_parent(item) == NULL || ritem->url == NULL )
79                 return;
80
81         ritem->refresh_id = 0;
82
83         /* Start automatic refresh timer, if necessary */
84         if( ritem->default_refresh_interval ) {
85                 rsprefs = rssyl_prefs_get();
86                 if( !rsprefs->refresh_enabled )
87                         return;
88
89                 ritem->refresh_interval = rsprefs->refresh;
90         }
91
92         /* Start the timer, if determined interval is >0 */
93         if( ritem->refresh_interval > 0 )
94                 rssyl_feed_start_refresh_timeout(ritem);
95 }
96
97 static void rssyl_make_rc_dir(void)
98 {
99         gchar *rssyl_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
100                         NULL);
101
102         if( !is_dir_exist(rssyl_dir) ) {
103                 if( make_dir(rssyl_dir) < 0 ) {
104                         g_warning("couldn't create directory %s", rssyl_dir);
105                 }
106
107                 debug_print("RSSyl: created directory %s\n", rssyl_dir);
108         }
109
110         g_free(rssyl_dir);
111 }
112
113 static void rssyl_create_default_mailbox(void)
114 {
115         Folder *root = NULL;
116
117         rssyl_make_rc_dir();
118
119         root = folder_new(rssyl_folder_get_class(), RSSYL_DEFAULT_MAILBOX, NULL);
120
121         g_return_if_fail(root != NULL);
122         folder_add(root);
123
124         rssyl_scan_tree(root);
125
126         /* FIXME: subscribe default feed */
127 //      rssyl_subscribe_new_feed(item, RSSYL_DEFAULT_FEED, TRUE);
128 }
129
130 static gboolean rssyl_update_all_feeds_deferred(gpointer data)
131 {
132         rssyl_update_all_feeds();
133         return FALSE;
134 }
135
136 static void rssyl_toolbar_cb_refresh_all_feeds(gpointer parent, const gchar *item_name, gpointer data)
137 {
138         rssyl_update_all_feeds();
139 }
140
141 void rssyl_init(void)
142 {
143         folder_register_class(rssyl_folder_get_class());
144
145         rssyl_gtk_init();
146         rssyl_make_rc_dir();
147
148         rssyl_prefs_init();
149
150         folder_func_to_all_folders((FolderItemFunc)rssyl_init_read_func, NULL);
151
152         if( !existing_tree_found )
153                 rssyl_create_default_mailbox();
154         else
155                 rssyl_update_format();
156
157         prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, PLUGIN_NAME, _("Refresh all feeds"), rssyl_toolbar_cb_refresh_all_feeds, NULL);
158
159         if( rssyl_prefs_get()->refresh_on_startup &&
160                         !prefs_common_get_prefs()->work_offline &&
161                         claws_is_starting() )
162                 g_timeout_add(2000, rssyl_update_all_feeds_deferred, NULL);
163 }
164
165 void rssyl_done(void)
166 {
167         rssyl_opml_export();
168
169         prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, PLUGIN_NAME, _("Refresh all feeds"));
170
171         rssyl_prefs_done();
172         rssyl_gtk_done();
173
174         if( !claws_is_exiting() )
175                 folder_unregister_class(rssyl_folder_get_class());
176
177         debug_print("RSSyl is done\n");
178 }
179
180 static gchar *rssyl_get_new_msg_filename(FolderItem *dest)
181 {
182         gchar *destfile;
183         gchar *destpath;
184
185         destpath = folder_item_get_path(dest);
186         g_return_val_if_fail(destpath != NULL, NULL);
187
188         if( !is_dir_exist(destpath) )
189                 make_dir_hier(destpath);
190
191         for( ; ; ) {
192                 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
193                                 dest->last_num + 1);
194                 if( is_file_entry_exist(destfile) ) {
195                         dest->last_num++;
196                         g_free(destfile);
197                 } else
198                         break;
199         }
200
201         g_free(destpath);
202
203         return destfile;
204 }
205
206 static void rssyl_get_last_num(Folder *folder, FolderItem *item)
207 {
208         gchar *path;
209         const char *f;
210         GDir *dp;
211         GError *error = NULL;
212         gint max = 0;
213         gint num;
214
215         g_return_if_fail(item != NULL);
216
217         debug_print("rssyl_get_last_num(): Scanning %s ...\n", item->path);
218         path = folder_item_get_path(item);
219         g_return_if_fail(path != NULL);
220
221         if( (dp = g_dir_open(path, 0, &error)) == NULL ) {
222                 FILE_OP_ERROR(item->path, "g_dir_open");
223                 debug_print("g_dir_open() failed on \"%s\", error %d (%s).\n",
224                                 path, error->code, error->message);
225                 g_error_free(error);
226                 g_free(path);
227                 return;
228         }
229
230         g_free(path);
231
232         while( (f = g_dir_read_name(dp)) != NULL) {
233                 if ((num = to_number(f)) > 0 &&
234                                 g_file_test(f, G_FILE_TEST_IS_REGULAR)) {
235                         if( max < num )
236                                 max = num;
237                 }
238         }
239         g_dir_close(dp);
240
241         debug_print("Last number in dir %s = %d\n", item->path, max);
242         item->last_num = max;
243 }
244
245 static Folder *rssyl_new_folder(const gchar *name, const gchar *path)
246 {
247         Folder *folder;
248
249         debug_print("RSSyl: new_folder: %s (%s)\n", name, path);
250
251         rssyl_make_rc_dir();
252
253         folder = g_new0(Folder, 1);
254         FOLDER(folder)->klass = &rssyl_class;
255         folder_init(FOLDER(folder), name);
256
257         return FOLDER(folder);
258 }
259
260 static void rssyl_destroy_folder(Folder *folder)
261 {
262         folder_local_folder_destroy(LOCAL_FOLDER(folder));
263 }
264
265 static void rssyl_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
266 {
267         GList *cur;
268         RFolderItem *ritem = (RFolderItem *)item;
269
270         folder_item_set_xml(folder, item, tag);
271
272         for( cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
273                 XMLAttr *attr = (XMLAttr *) cur->data;
274
275                 if( !attr || !attr->name || !attr->value)
276                         continue;
277
278                 /* (str) URL */
279                 if( !strcmp(attr->name, "uri")) {
280                         g_free(ritem->url);
281                         ritem->url = g_strdup(attr->value);
282                 }
283                 /* (int) URL auth */
284                 if (!strcmp(attr->name, "auth")) {
285                         ritem->auth->type = atoi(attr->value);
286                 }
287                 /* (str) Auth user */
288                 if (!strcmp(attr->name, "auth_user")) {
289                         g_free(ritem->auth->username);
290                         ritem->auth->username = g_strdup(attr->value);
291                 }
292                 /* (str) Auth pass - save directly to password store */
293                 if (!strcmp(attr->name, "auth_pass")) {
294                         gsize len = 0;
295                         guchar *pwd = g_base64_decode(attr->value, &len);
296                         memset(attr->value, 0, strlen(attr->value));
297                         rssyl_passwd_set(ritem, (gchar *)pwd);
298                         memset(pwd, 0, strlen(pwd));
299                         g_free(pwd);
300                 }
301                 /* (str) Official title */
302                 if( !strcmp(attr->name, "official_title")) {
303                         g_free(ritem->official_title);
304                         ritem->official_title = g_strdup(attr->value);
305                 }
306                 /* (bool) Keep old items */
307                 if( !strcmp(attr->name, "keep_old"))
308                         ritem->keep_old = (atoi(attr->value) == 0 ? FALSE : TRUE );
309                 /* (bool) Use default refresh_interval */
310                 if( !strcmp(attr->name, "default_refresh_interval"))
311                         ritem->default_refresh_interval = (atoi(attr->value) == 0 ? FALSE : TRUE );
312                 /* (int) Refresh interval */
313                 if( !strcmp(attr->name, "refresh_interval"))
314                         ritem->refresh_interval = atoi(attr->value);
315                 /* (bool) Fetch comments */
316                 if( !strcmp(attr->name, "fetch_comments"))
317                         ritem->fetch_comments = (atoi(attr->value) == 0 ? FALSE : TRUE );
318                 /* (int) Max age of posts to fetch comments for */
319                 if( !strcmp(attr->name, "fetch_comments_max_age"))
320                         ritem->fetch_comments_max_age = atoi(attr->value);
321                 /* (bool) Write heading */
322                 if( !strcmp(attr->name, "write_heading"))
323                         ritem->write_heading = (atoi(attr->value) == 0 ? FALSE : TRUE );
324                 /* (int) Silent update */
325                 if( !strcmp(attr->name, "silent_update"))
326                         ritem->silent_update = atoi(attr->value);
327                 /* (bool) Ignore title rename */
328                 if( !strcmp(attr->name, "ignore_title_rename"))
329                         ritem->ignore_title_rename = (atoi(attr->value) == 0 ? FALSE : TRUE );
330                 /* (bool) Verify SSL peer  */
331                 if( !strcmp(attr->name, "ssl_verify_peer"))
332                         ritem->ssl_verify_peer = (atoi(attr->value) == 0 ? FALSE : TRUE );
333         }
334 }
335
336 static XMLTag *rssyl_item_get_xml(Folder *folder, FolderItem *item)
337 {
338         XMLTag *tag;
339         RFolderItem *ri = (RFolderItem *)item;
340         gchar *tmp = NULL;
341
342         tag = folder_item_get_xml(folder, item);
343
344         /* (str) URL */
345         if( ri->url != NULL )
346                 xml_tag_add_attr(tag, xml_attr_new("uri", ri->url));
347         /* (int) Auth */
348         tmp = g_strdup_printf("%d", ri->auth->type);
349         xml_tag_add_attr(tag, xml_attr_new("auth", tmp));
350         g_free(tmp);
351         /* (str) Auth user */
352         if (ri->auth->username != NULL)
353                 xml_tag_add_attr(tag, xml_attr_new("auth_user", ri->auth->username));
354         /* (str) Official title */
355         if( ri->official_title != NULL )
356                 xml_tag_add_attr(tag, xml_attr_new("official_title", ri->official_title));
357         /* (bool) Keep old items */
358         xml_tag_add_attr(tag, xml_attr_new("keep_old",
359                                 (ri->keep_old ? "1" : "0")) );
360         /* (bool) Use default refresh interval */
361         xml_tag_add_attr(tag, xml_attr_new("default_refresh_interval",
362                                 (ri->default_refresh_interval ? "1" : "0")) );
363         /* (int) Refresh interval */
364         tmp = g_strdup_printf("%d", ri->refresh_interval);
365         xml_tag_add_attr(tag, xml_attr_new("refresh_interval", tmp));
366         g_free(tmp);
367         /* (bool) Fetch comments */
368         xml_tag_add_attr(tag, xml_attr_new("fetch_comments",
369                                 (ri->fetch_comments ? "1" : "0")) );
370         /* (int) Max age of posts to fetch comments for */
371         tmp = g_strdup_printf("%d", ri->fetch_comments_max_age);
372         xml_tag_add_attr(tag, xml_attr_new("fetch_comments_max_age", tmp));
373         g_free(tmp);
374         /* (bool) Write heading */
375         xml_tag_add_attr(tag, xml_attr_new("write_heading",
376                                 (ri->write_heading ? "1" : "0")) );
377         /* (int) Silent update */
378         tmp = g_strdup_printf("%d", ri->silent_update);
379         xml_tag_add_attr(tag, xml_attr_new("silent_update", tmp));
380         g_free(tmp);
381         /* (bool) Ignore title rename */
382         xml_tag_add_attr(tag, xml_attr_new("ignore_title_rename",
383                                 (ri->ignore_title_rename ? "1" : "0")) );
384         /* (bool) Verify SSL peer */
385         xml_tag_add_attr(tag, xml_attr_new("ssl_verify_peer",
386                                 (ri->ssl_verify_peer ? "1" : "0")) );
387
388         return tag;
389 }
390
391 static gint rssyl_scan_tree(Folder *folder)
392 {
393         g_return_val_if_fail(folder != NULL, -1);
394
395         folder->outbox = NULL;
396         folder->draft = NULL;
397         folder->queue = NULL;
398         folder->trash = NULL;
399
400         debug_print("RSSyl: scanning tree\n");
401         rssyl_create_tree(folder);
402
403         return 0;
404 }
405
406 static gint rssyl_create_tree(Folder *folder)
407 {
408         FolderItem *rootitem;
409         GNode *rootnode;
410
411         g_return_val_if_fail(folder != NULL, -1);
412
413         rssyl_make_rc_dir();
414
415         if( !folder->node ) {
416                 rootitem = folder_item_new(folder, folder->name, NULL);
417                 rootitem->folder = folder;
418                 rootnode = g_node_new(rootitem);
419                 folder->node = rootnode;
420                 rootitem->node = rootnode;
421         }
422
423         debug_print("RSSyl: created new rssyl tree\n");
424         return 0;
425 }
426
427 static FolderItem *rssyl_item_new(Folder *folder)
428 {
429         RFolderItem *ritem = g_new0(RFolderItem, 1);
430
431         ritem->url = NULL;
432         ritem->auth = g_new0(FeedAuth, 1);
433         ritem->auth->type = FEED_AUTH_NONE;
434         ritem->auth->username = NULL;
435         ritem->auth->password = NULL;
436         ritem->official_title = NULL;
437         ritem->source_id = NULL;
438         ritem->items = NULL;
439         ritem->deleted_items = NULL;
440         ritem->keep_old = TRUE;
441         ritem->default_refresh_interval = TRUE;
442         ritem->refresh_interval = atoi(PREF_DEFAULT_REFRESH);
443         ritem->fetch_comments = FALSE;
444         ritem->fetch_comments_max_age = -1;
445         ritem->write_heading = TRUE;
446         ritem->fetching_comments = FALSE;
447         ritem->silent_update = 0;
448         ritem->last_update = 0;
449         ritem->ignore_title_rename = FALSE;
450         ritem->ssl_verify_peer = TRUE;
451         ritem->feedprop = NULL;
452         ritem->refresh_id = 0;
453
454         return (FolderItem *)ritem;
455 }
456
457 static void rssyl_item_destroy(Folder *folder, FolderItem *item)
458 {
459         RFolderItem *ritem = (RFolderItem *)item;
460
461         g_return_if_fail(ritem != NULL);
462
463         g_free(ritem->url);
464         if (ritem->auth->username)
465                 g_free(ritem->auth->username);
466         if (ritem->auth->password)
467                 g_free(ritem->auth->password);
468         g_free(ritem->auth);
469         g_free(ritem->official_title);
470         g_slist_free(ritem->items);
471
472         /* Remove a scheduled refresh, if any */
473         if( ritem->refresh_id != 0)
474                 g_source_remove(ritem->refresh_id);
475
476         g_free(ritem);
477 }
478
479 static FolderItem *rssyl_create_folder(Folder *folder,
480                                                                 FolderItem *parent, const gchar *name)
481 {
482         gchar *path = NULL, *basepath = NULL, *itempath = NULL;
483         FolderItem *newitem = NULL;
484
485         g_return_val_if_fail(folder != NULL, NULL);
486         g_return_val_if_fail(parent != NULL, NULL);
487         g_return_val_if_fail(name != NULL, NULL);
488
489         path = folder_item_get_path(parent);
490         if( !is_dir_exist(path) ) {
491                 if( (make_dir_hier(path) != 0) ) {
492                         debug_print("RSSyl: Couldn't create directory (rec) '%s'\n", path);
493                         return NULL;
494                 }
495         }
496
497         basepath = g_strdelimit(g_strdup(name), G_DIR_SEPARATOR_S, '_');
498         path = g_strconcat(path, G_DIR_SEPARATOR_S, basepath, NULL);
499
500         if( make_dir(path) < 0 ) {
501                 debug_print("RSSyl: Couldn't create directory '%s'\n", path);
502                 g_free(path);
503                 g_free(basepath);
504                 return NULL;
505         }
506         g_free(path);
507
508         itempath = g_strconcat((parent->path ? parent->path : ""),
509                         G_DIR_SEPARATOR_S, basepath, NULL);
510         newitem = folder_item_new(folder, name, itempath);
511         g_free(itempath);
512         g_free(basepath);
513
514         folder_item_append(parent, newitem);
515
516         return newitem;
517 }
518
519 FolderItem *rssyl_get_root_folderitem(FolderItem *item)
520 {
521         FolderItem *i;
522
523         for( i = item; folder_item_parent(i) != NULL; i = folder_item_parent(i) ) { }
524         return i;
525 }
526
527 static gchar *rssyl_item_get_path(Folder *folder, FolderItem *item)
528 {
529         gchar *path, *name;
530
531         g_return_val_if_fail(folder != NULL, NULL);
532         g_return_val_if_fail(item != NULL, NULL);
533
534         name = folder_item_get_name(rssyl_get_root_folderitem(item));
535         path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
536                         G_DIR_SEPARATOR_S, name, item->path, NULL);
537         g_free(name);
538
539         return path;
540 }
541
542 static gboolean rssyl_rename_folder_func(GNode *node, gpointer data)
543 {
544         FolderItem *item = node->data;
545         gchar **paths = data;
546         const gchar *oldpath = paths[0];
547         const gchar *newpath = paths[1];
548         gchar *base;
549         gchar *new_itempath;
550         gint oldpathlen;
551
552         oldpathlen = strlen(oldpath);
553         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
554                 g_warning("path doesn't match: %s, %s", oldpath, item->path);
555                 return TRUE;
556         }
557
558         base = item->path + oldpathlen;
559         while (*base == G_DIR_SEPARATOR) base++;
560         if (*base == '\0')
561                 new_itempath = g_strdup(newpath);
562         else
563                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
564                                 NULL);
565         g_free(item->path);
566         item->path = new_itempath;
567
568         return FALSE;
569 }
570
571 static gint rssyl_rename_folder(Folder *folder, FolderItem *item,
572                                 const gchar *name)
573 {
574         gchar *oldpath;
575         gchar *dirname;
576         gchar *newpath, *utf8newpath;
577         gchar *basenewpath;
578         gchar *paths[2];
579
580         g_return_val_if_fail(folder != NULL, -1);
581         g_return_val_if_fail(item != NULL, -1);
582         g_return_val_if_fail(item->path != NULL, -1);
583         g_return_val_if_fail(name != NULL, -1);
584
585         debug_print("RSSyl: rssyl_rename_folder '%s' -> '%s'\n",
586                         item->name, name);
587
588         if (!strcmp(item->name, name))
589                         return 0;
590
591         oldpath = folder_item_get_path(item);
592         if( !is_dir_exist(oldpath) )
593                 make_dir_hier(oldpath);
594
595         dirname = g_path_get_dirname(oldpath);
596         basenewpath = g_strdelimit(g_strdup(name), G_DIR_SEPARATOR_S, '_');
597         newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, basenewpath, NULL);
598         g_free(dirname);
599         g_free(basenewpath);
600
601         if( g_rename(oldpath, newpath) < 0 ) {
602                 FILE_OP_ERROR(oldpath, "rename");
603                 g_free(oldpath);
604                 g_free(newpath);
605                 return -1;
606         }
607
608         g_free(oldpath);
609         g_free(newpath);
610
611         if( strchr(item->path, G_DIR_SEPARATOR) != NULL ) {
612                 dirname = g_path_get_dirname(item->path);
613                 utf8newpath = g_strconcat(dirname, G_DIR_SEPARATOR_S, name, NULL);
614                 g_free(dirname);
615         } else
616                 utf8newpath = g_strdup(name);
617
618         g_free(item->name);
619         item->name = g_strdup(name);
620
621         paths[0] = g_strdup(item->path);
622         paths[1] = utf8newpath;
623         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
624                         rssyl_rename_folder_func, paths);
625
626         g_free(paths[0]);
627         g_free(paths[1]);
628
629         return 0;
630 }
631
632 static gint rssyl_remove_folder(Folder *folder, FolderItem *item)
633 {
634         gchar *path = NULL;
635         RFolderItem *ritem = (RFolderItem *)item;
636
637         g_return_val_if_fail(folder != NULL, -1);
638         g_return_val_if_fail(item != NULL, -1);
639         g_return_val_if_fail(item->path != NULL, -1);
640         g_return_val_if_fail(item->stype == F_NORMAL, -1);
641
642         debug_print("RSSyl: removing folder item %s\n", item->path);
643
644         path = folder_item_get_path(item);
645         if( remove_dir_recursive(path) < 0 ) {
646                 g_warning("can't remove directory '%s'", path);
647                 g_free(path);
648                 return -1;
649         }
650         g_free(path);
651
652         if (ritem->url != NULL)
653                 rssyl_passwd_set(ritem, NULL);
654
655         folder_item_remove(item);
656
657         return 0;
658 }
659
660 static gint rssyl_get_num_list(Folder *folder, FolderItem *item,
661                 MsgNumberList **list, gboolean *old_uids_valid)
662 {
663         gchar *path;
664         GDir *dp;
665         const gchar *d;
666         GError *error = NULL;
667         gint num, nummsgs = 0;
668
669         g_return_val_if_fail(item != NULL, -1);
670
671         debug_print("RSSyl: get_num_list: scanning '%s'\n", item->path);
672
673         *old_uids_valid = TRUE;
674         
675         path = folder_item_get_path(item);
676         g_return_val_if_fail(path != NULL, -1);
677
678         if( (dp = g_dir_open(path, 0, &error)) == NULL ) {
679                 debug_print("g_dir_open() failed on \"%s\", error %d (%s).\n",
680                                 path, error->code, error->message);
681                 g_error_free(error);
682                 g_free(path);
683                 return -1;
684         }
685
686         g_free(path);
687
688         while( (d = g_dir_read_name(dp)) != NULL ) {
689                 if( (num = to_number(d)) > 0 ) {
690                         *list = g_slist_prepend(*list, GINT_TO_POINTER(num));
691                         nummsgs++;
692                 }
693         }
694         g_dir_close(dp);
695
696         debug_print("RSSyl: get_num_list: returning %d\n", nummsgs);
697
698         return nummsgs;
699 }
700
701 static gboolean rssyl_is_msg_changed(Folder *folder, FolderItem *item,
702                 MsgInfo *msginfo)
703 {
704         GStatBuf s;
705         gchar *path = NULL;
706         gchar *itempath = NULL;
707
708         g_return_val_if_fail(folder != NULL, FALSE);
709         g_return_val_if_fail(item != NULL, FALSE);
710         g_return_val_if_fail(msginfo != NULL, FALSE);
711
712         itempath = folder_item_get_path(item);
713         path = g_strconcat(itempath, G_DIR_SEPARATOR_S, itos(msginfo->msgnum), NULL);
714         g_free(itempath);
715
716         if (g_stat(path, &s) < 0 ||
717                 msginfo->size != s.st_size || (
718                                 (msginfo->mtime - s.st_mtime != 0) &&
719                                 (msginfo->mtime - s.st_mtime != 3600) &&
720                                 (msginfo->mtime - s.st_mtime != -3600))) {
721                 g_free(path);
722                 return TRUE;
723         }
724
725         g_free(path);
726         return FALSE;
727 }
728
729 static gchar *rssyl_fetch_msg(Folder *folder, FolderItem *item, gint num)
730 {
731         gchar *path;
732         gchar *file;
733
734         g_return_val_if_fail(item != NULL, NULL);
735         g_return_val_if_fail(num > 0, NULL);
736
737         path = folder_item_get_path(item);
738         file = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
739         g_free(path);
740
741         debug_print("RSSyl: fetch_msg '%s'\n", file);
742
743         if( !is_file_exist(file)) {
744                 g_free(file);
745                 return NULL;
746         }
747
748         return file;
749 }
750
751 static MsgInfo *rssyl_get_msginfo(Folder *folder, FolderItem *item, gint num)
752 {
753         MsgInfo *msginfo = NULL;
754         gchar *file;
755         MsgFlags flags;
756
757         g_return_val_if_fail(folder != NULL, NULL);
758         g_return_val_if_fail(item != NULL, NULL);
759         g_return_val_if_fail(num > 0, NULL);
760
761         debug_print("RSSyl: get_msginfo: %d\n", num);
762
763         file = rssyl_fetch_msg(folder, item, num);
764         g_return_val_if_fail(file != NULL, NULL);
765
766         flags.perm_flags = 0;
767         flags.tmp_flags = 0;
768
769         msginfo = rssyl_feed_parse_item_to_msginfo(file, flags, TRUE, TRUE, item);
770         g_free(file);
771
772         if( msginfo )
773                 msginfo->msgnum = num;
774
775         return msginfo;
776 }
777
778 static gint rssyl_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
779                 GHashTable *relation)
780 {
781         gchar *destfile;
782         GSList *cur;
783         MsgFileInfo *fileinfo;
784
785         g_return_val_if_fail(dest != NULL, -1);
786         g_return_val_if_fail(file_list != NULL, -1);
787
788         if( dest->last_num < 0 ) {
789                 rssyl_get_last_num(folder, dest);
790                 if( dest->last_num < 0 ) return -1;
791         }
792
793         for( cur = file_list; cur != NULL; cur = cur->next ) {
794                 fileinfo = (MsgFileInfo *)cur->data;
795
796                 destfile = rssyl_get_new_msg_filename(dest);
797                 g_return_val_if_fail(destfile != NULL, -1);
798                 debug_print("RSSyl: add_msgs: new filename is '%s'\n", destfile);
799
800                 if( copy_file(fileinfo->file, destfile, TRUE) < 0 ) {
801                         g_warning("can't copy message %s to %s", fileinfo->file, destfile);
802                         g_free(destfile);
803                         return -1;
804                 }
805
806                 if( relation != NULL )
807                         g_hash_table_insert(relation, fileinfo->msginfo != NULL ?
808                                         (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
809                                         GINT_TO_POINTER(dest->last_num + 1));
810                 g_free(destfile);
811                 dest->last_num++;
812         }
813
814
815         return dest->last_num;
816 }
817
818 gint rssyl_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
819                 MsgFlags *flags)
820 {
821         GSList file_list;
822         MsgFileInfo fileinfo;
823
824         g_return_val_if_fail(file != NULL, -1);
825
826         fileinfo.msginfo = NULL;
827         fileinfo.file = (gchar *)file;
828         fileinfo.flags = flags;
829         file_list.data = &fileinfo;
830         file_list.next = NULL;
831
832         return rssyl_add_msgs(folder, dest, &file_list, NULL);
833 }
834
835 static gint rssyl_remove_msg(Folder *folder, FolderItem *item, gint num)
836 {
837         gboolean need_scan = FALSE;
838         gchar *file, *tmp;
839         RFolderItem *ritem = (RFolderItem *)item;
840
841         g_return_val_if_fail(item != NULL, -1);
842
843         file = rssyl_fetch_msg(folder, item, num);
844         g_return_val_if_fail(file != NULL, -1);
845
846         need_scan = mh_get_class()->scan_required(folder, item);
847
848         /* are we doing a folder move ? */
849         tmp = g_strdup_printf("%s.tmp", file);
850         if (is_file_exist(tmp)) {
851                 g_unlink(tmp);
852                 g_free(tmp);
853                 g_free(file);
854                 return 0;
855         }
856         g_free(tmp);
857
858         rssyl_deleted_update(ritem);
859         rssyl_deleted_add(ritem, file);
860         rssyl_deleted_store(ritem);
861         rssyl_deleted_free(ritem);
862
863         if( g_unlink(file) < 0 ) {
864                 FILE_OP_ERROR(file, "unlink");
865                 g_free(file);
866                 return -1;
867         }
868
869         if( !need_scan )
870                 item->mtime = time(NULL);
871
872         g_free(file);
873         return 0;
874 }
875
876 static gint rssyl_remove_msgs(Folder *folder, FolderItem *item,
877                 MsgInfoList *msglist, GHashTable *relation)
878 {
879         gboolean need_scan = FALSE;
880         MsgInfoList *cur;
881         gint processed = 0;
882
883         RFolderItem *ritem = (RFolderItem *)item;
884
885         g_return_val_if_fail(item != NULL, -1);
886
887         need_scan = mh_get_class()->scan_required(folder, item);
888
889         rssyl_deleted_update(ritem);
890
891         for (cur = msglist; cur != NULL; cur = cur->next) {
892                 gchar *file;
893                 MsgInfo *msginfo = (MsgInfo *)cur->data;
894
895                 if (msginfo == NULL)
896                         continue;
897
898                 file = rssyl_fetch_msg(folder, item, msginfo->msgnum);
899                 if (file == NULL)
900                         continue;
901
902                 rssyl_deleted_add(ritem, file);
903
904                 if (claws_unlink(file) < 0) {
905                         FILE_OP_ERROR(file, "unlink");
906                 }
907
908                 g_free(file);
909                 processed++;
910         }
911
912         if (processed > 0)
913                 rssyl_deleted_store(ritem);
914         rssyl_deleted_free(ritem);
915
916         if (!need_scan)
917                 item->mtime = time(NULL);
918
919         return processed;
920 }
921
922 static gboolean rssyl_subscribe_uri(Folder *folder, const gchar *uri)
923 {
924         if (folder->klass != rssyl_folder_get_class())
925                 return FALSE;
926         return (rssyl_subscribe(FOLDER_ITEM(folder->node->data), uri, 0) ?
927                         TRUE : FALSE);
928 }
929
930 static void rssyl_copy_private_data(Folder *folder, FolderItem *oldi,
931                 FolderItem *newi)
932 {
933         gchar *dpathold, *dpathnew;
934         RFolderItem *olditem = (RFolderItem *)oldi,
935                                                                         *newitem = (RFolderItem *)newi;
936
937         g_return_if_fail(folder != NULL);
938         g_return_if_fail(olditem != NULL);
939         g_return_if_fail(newitem != NULL);
940
941         if (olditem->url != NULL) {
942                 g_free(newitem->url);
943                 newitem->url = g_strdup(olditem->url);
944         }
945
946         if (olditem->auth != NULL) {
947                 if (newitem->auth != NULL) {
948                         if (newitem->auth->username != NULL) {
949                                 g_free(newitem->auth->username);
950                                 newitem->auth->username = NULL;
951                         }
952                         if (newitem->auth->password != NULL) {
953                                 g_free(newitem->auth->password);
954                                 newitem->auth->password = NULL;
955                         }
956                         g_free(newitem->auth);
957                 }
958                 newitem->auth = g_new0(FeedAuth, 1);
959                 newitem->auth->type = olditem->auth->type;
960                 if (olditem->auth->username != NULL)
961                         newitem->auth->username = g_strdup(olditem->auth->username);
962                 if (olditem->auth->password != NULL)
963                         newitem->auth->password = g_strdup(olditem->auth->password);
964         }
965
966         if (olditem->official_title != NULL) {
967                 g_free(newitem->official_title);
968                 newitem->official_title = g_strdup(olditem->official_title);
969         }
970
971         if (olditem->source_id != NULL) {
972                 g_free(newitem->source_id);
973                 newitem->source_id = g_strdup(olditem->source_id);
974         }
975
976         newitem->keep_old = olditem->keep_old;
977         newitem->default_refresh_interval = olditem->default_refresh_interval;
978         newitem->refresh_interval = olditem->refresh_interval;
979         newitem->fetch_comments = olditem->fetch_comments;
980         newitem->fetch_comments_max_age = olditem->fetch_comments_max_age;
981         newitem->silent_update = olditem->silent_update;
982         newitem->write_heading = olditem->write_heading;
983         newitem->ignore_title_rename = olditem->ignore_title_rename;
984         newitem->ssl_verify_peer = olditem->ssl_verify_peer;
985         newitem->refresh_id = olditem->refresh_id;
986         newitem->fetching_comments = olditem->fetching_comments;
987         newitem->last_update = olditem->last_update;
988
989         dpathold = g_strconcat(rssyl_item_get_path(oldi->folder, oldi),
990                         G_DIR_SEPARATOR_S, RSSYL_DELETED_FILE, NULL);
991         dpathnew = g_strconcat(rssyl_item_get_path(newi->folder, newi),
992                         G_DIR_SEPARATOR_S, RSSYL_DELETED_FILE, NULL);
993         move_file(dpathold, dpathnew, TRUE);
994         g_free(dpathold);
995         g_free(dpathnew);
996
997 }
998
999 /************************************************************************/
1000
1001 FolderClass *rssyl_folder_get_class()
1002 {
1003         if( rssyl_class.idstr == NULL ) {
1004                 rssyl_class.type = F_UNKNOWN;
1005                 rssyl_class.idstr = "rssyl";
1006                 rssyl_class.uistr = PLUGIN_NAME;
1007
1008                 /* Folder functions */
1009                 rssyl_class.new_folder = rssyl_new_folder;
1010                 rssyl_class.destroy_folder = rssyl_destroy_folder;
1011                 rssyl_class.set_xml = folder_set_xml;
1012                 rssyl_class.get_xml = folder_get_xml;
1013                 rssyl_class.scan_tree = rssyl_scan_tree;
1014                 rssyl_class.create_tree = rssyl_create_tree;
1015
1016                 /* FolderItem functions */
1017                 rssyl_class.item_new = rssyl_item_new;
1018                 rssyl_class.item_destroy = rssyl_item_destroy;
1019                 rssyl_class.item_get_path = rssyl_item_get_path;
1020                 rssyl_class.create_folder = rssyl_create_folder;
1021                 rssyl_class.rename_folder = rssyl_rename_folder;
1022                 rssyl_class.remove_folder = rssyl_remove_folder;
1023                 rssyl_class.get_num_list = rssyl_get_num_list;
1024                 rssyl_class.scan_required = mh_get_class()->scan_required;
1025                 rssyl_class.item_set_xml = rssyl_item_set_xml;
1026                 rssyl_class.item_get_xml = rssyl_item_get_xml;
1027
1028                 /* Message functions */
1029                 rssyl_class.get_msginfo = rssyl_get_msginfo;
1030                 rssyl_class.fetch_msg = rssyl_fetch_msg;
1031                 rssyl_class.copy_msg = mh_get_class()->copy_msg;
1032                 rssyl_class.copy_msgs = mh_get_class()->copy_msgs;
1033                 rssyl_class.add_msg = rssyl_add_msg;
1034                 rssyl_class.add_msgs = rssyl_add_msgs;
1035                 rssyl_class.remove_msg = rssyl_remove_msg;
1036                 rssyl_class.remove_msgs = rssyl_remove_msgs;
1037                 rssyl_class.is_msg_changed = rssyl_is_msg_changed;
1038 //              rssyl_class.change_flags = rssyl_change_flags;
1039                 rssyl_class.change_flags = NULL;
1040                 rssyl_class.subscribe = rssyl_subscribe_uri;
1041                 rssyl_class.copy_private_data = rssyl_copy_private_data;
1042                 rssyl_class.search_msgs = folder_item_search_msgs_local;
1043         }
1044
1045         return &rssyl_class;
1046 }