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