Fix error creation and warning
[claws.git] / src / plugins / vcalendar / vcal_folder.c
blob3c37a6464aacf3c7a6fee2b154a89209ffa45aaf
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 Colin Leroy <colin@colino.net> and
4 * the Claws Mail team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #include "claws-features.h"
23 #endif
25 #include <stddef.h>
26 #include <glib.h>
27 #include <glib/gi18n.h>
29 #include "defs.h"
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <curl/curl.h>
36 #include <curl/curlver.h>
37 #include <ctype.h>
39 #include "account.h"
40 #include "file-utils.h"
41 #include "utils.h"
42 #include "procmsg.h"
43 #include "procheader.h"
44 #include "folder.h"
45 #include "folderview.h"
46 #include "folder_item_prefs.h"
47 #include "vcalendar.h"
48 #include "vcal_folder.h"
49 #include "vcal_prefs.h"
50 #include "vcal_manager.h"
51 #include "vcal_meeting_gtk.h"
52 #include "vcal_interface.h"
53 #include "prefs_account.h"
54 #include "prefs_common.h"
55 #include "claws.h"
56 #include "menu.h"
57 #include "inputdialog.h"
58 #include "inc.h"
59 #include "xml.h"
60 #include "alertpanel.h"
61 #include "log.h"
62 #include "mainwindow.h"
63 #include "statusbar.h"
64 #include "msgcache.h"
65 #include "passwordstore.h"
66 #include "timing.h"
67 #include "messageview.h"
69 #include <gtk/gtk.h>
70 #include <dirent.h>
72 #ifdef USE_PTHREAD
73 #include <pthread.h>
74 #endif
76 typedef struct _thread_data {
77 const gchar *url;
78 gchar *result;
79 gchar *error;
80 gboolean done;
81 } thread_data;
83 typedef struct _IcalFeedData {
84 icalcomponent *event;
85 gchar *pseudoevent_id;
86 } IcalFeedData;
88 typedef struct _VCalFolder VCalFolder;
89 typedef struct _VCalFolderItem VCalFolderItem;
91 static Folder *vcal_folder_new(const gchar * name,
92 const gchar * folder);
93 static void vcal_folder_destroy(Folder * folder);
94 static void vcal_item_destroy(Folder *folder, FolderItem *_item);
95 static gchar *vcal_item_get_path(Folder *folder, FolderItem *item);
97 static gint vcal_scan_tree(Folder * folder);
98 static FolderItem *vcal_item_new(Folder * folder);
99 static gint vcal_get_num_list(Folder * folder, FolderItem * item,
100 MsgNumberList ** list,
101 gboolean * old_uids_valid);
102 static MsgInfo *vcal_get_msginfo(Folder * folder, FolderItem * item,
103 gint num);
104 static gchar *vcal_fetch_msg(Folder * folder, FolderItem * item,
105 gint num);
106 static gint vcal_add_msg(Folder * folder, FolderItem * _dest,
107 const gchar * file, MsgFlags * flags);
108 static gint vcal_remove_msg(Folder * folder, FolderItem * _item,
109 gint num);
110 static FolderItem *vcal_create_folder(Folder * folder,
111 FolderItem * parent,
112 const gchar * name);
113 static gint vcal_create_tree(Folder *folder);
114 static gint vcal_remove_folder(Folder *folder, FolderItem *item);
115 static gboolean vcal_scan_required(Folder *folder, FolderItem *item);
116 static void vcal_set_mtime(Folder *folder, FolderItem *item);
117 static void vcal_change_flags(Folder *folder, FolderItem *_item, MsgInfo *msginfo, MsgPermFlags newflags);
119 static void new_meeting_cb(GtkAction *action, gpointer data);
120 static void export_cal_cb(GtkAction *action, gpointer data);
121 static void subscribe_cal_cb(GtkAction *action, gpointer data);
122 static void check_subs_cb(GtkAction *action, gpointer data);
123 static void unsubscribe_cal_cb(GtkAction *action, gpointer data);
124 static void rename_cb(GtkAction *action, gpointer data);
125 static void set_view_cb(GtkAction *action, GtkRadioAction *current, gpointer data);
127 static void add_menuitems(GtkUIManager *ui_manager, FolderItem *item);
128 static void set_sensitivity(GtkUIManager *ui_manager, FolderItem *item);
130 static void update_subscription(const gchar *uri, gboolean verbose);
131 static void vcal_folder_set_batch (Folder *folder,
132 FolderItem *item,
133 gboolean batch);
134 static void convert_to_utc(icalcomponent *calendar);
136 gboolean vcal_subscribe_uri(Folder *folder, const gchar *uri);
138 FolderClass vcal_class;
140 static GSList *created_files = NULL;
141 static GHashTable *hash_uids = NULL;
143 struct _VCalFolder
145 Folder folder;
148 struct _VCalFolderItem
150 FolderItem item;
151 gchar *uri;
152 gchar *feed;
153 icalcomponent *cal;
154 GSList *numlist;
155 GSList *evtlist;
156 gboolean batching;
157 gboolean dirty;
158 day_win *dw;
159 month_win *mw;
160 time_t last_fetch;
161 int use_cal_view;
164 static char *vcal_popup_labels[] =
166 N_("_New meeting..."),
167 N_("_Export calendar..."),
168 N_("_Subscribe to Webcal..."),
169 N_("_Unsubscribe..."),
170 N_("_Rename..."),
171 N_("U_pdate subscriptions"),
172 N_("_List view"),
173 N_("_Week view"),
174 N_("_Month view"),
175 NULL
178 static GtkActionEntry vcal_popup_entries[] =
180 {"FolderViewPopup/NewMeeting", NULL, NULL, NULL, NULL, G_CALLBACK(new_meeting_cb) },
181 {"FolderViewPopup/ExportCal", NULL, NULL, NULL, NULL, G_CALLBACK(export_cal_cb) },
183 {"FolderViewPopup/SubscribeCal", NULL, NULL, NULL, NULL, G_CALLBACK(subscribe_cal_cb) },
184 {"FolderViewPopup/UnsubscribeCal", NULL, NULL, NULL, NULL, G_CALLBACK(unsubscribe_cal_cb) },
186 {"FolderViewPopup/RenameFolder", NULL, NULL, NULL, NULL, G_CALLBACK(rename_cb) },
188 {"FolderViewPopup/CheckSubs", NULL, NULL, NULL, NULL, G_CALLBACK(check_subs_cb) },
192 static GtkRadioActionEntry vcal_popup_radio_entries[] = { /* set_view_cb */
193 {"FolderViewPopup/ListView", NULL, NULL, NULL, NULL, 0 },
194 {"FolderViewPopup/WeekView", NULL, NULL, NULL, NULL, 1 },
195 {"FolderViewPopup/MonthView", NULL, NULL, NULL, NULL, 2 },
198 static IcalFeedData *icalfeeddata_new(icalcomponent *evt, gchar *str)
200 IcalFeedData *data = g_new0(IcalFeedData, 1);
201 if (str)
202 data->pseudoevent_id = g_strdup(str);
203 data->event = evt;
204 return data;
207 static void icalfeeddata_free(IcalFeedData *data)
209 g_free(data->pseudoevent_id);
210 if (data->event)
211 icalcomponent_free(data->event);
212 g_free(data);
215 static void slist_free_icalfeeddata(GSList *list)
217 while (list) {
218 IcalFeedData *data = (IcalFeedData *)list->data;
219 icalfeeddata_free(data);
220 list = list->next;
224 static void vcal_fill_popup_menu_labels(void)
226 vcal_popup_entries[0].label = _(vcal_popup_labels[0]);
227 vcal_popup_entries[1].label = _(vcal_popup_labels[1]);
228 vcal_popup_entries[2].label = _(vcal_popup_labels[2]);
229 vcal_popup_entries[3].label = _(vcal_popup_labels[3]);
230 vcal_popup_entries[4].label = _(vcal_popup_labels[4]);
231 vcal_popup_entries[5].label = _(vcal_popup_labels[5]);
232 vcal_popup_radio_entries[0].label = _(vcal_popup_labels[6]);
233 vcal_popup_radio_entries[1].label = _(vcal_popup_labels[7]);
234 vcal_popup_radio_entries[2].label = _(vcal_popup_labels[8]);
237 static FolderViewPopup vcal_popup =
239 PLUGIN_NAME,
240 "<vCalendar>",
241 vcal_popup_entries,
242 G_N_ELEMENTS(vcal_popup_entries),
243 NULL, 0,
244 vcal_popup_radio_entries,
245 G_N_ELEMENTS(vcal_popup_radio_entries), 1, set_view_cb,
246 add_menuitems,
247 set_sensitivity
250 static void vcal_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
252 GList *cur;
253 folder_item_set_xml(folder, item, tag);
254 gboolean found_cal_view_setting = FALSE;
256 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
257 XMLAttr *attr = (XMLAttr *) cur->data;
259 if (!attr || !attr->name || !attr->value) continue;
260 if (!strcmp(attr->name, "uri")) {
261 if (((VCalFolderItem *)item)->uri != NULL)
262 g_free(((VCalFolderItem *)item)->uri);
263 ((VCalFolderItem *)item)->uri = g_strdup(attr->value);
265 if (!strcmp(attr->name, "use_cal_view")) {
266 found_cal_view_setting = TRUE;
267 ((VCalFolderItem *)item)->use_cal_view = atoi(attr->value);
270 if (((VCalFolderItem *)item)->uri == NULL) {
271 /* give a path to inbox */
272 g_free(item->path);
273 item->path = g_strdup(".meetings");
275 if (!found_cal_view_setting)
276 ((VCalFolderItem *)item)->use_cal_view = 1; /*week view */
280 static XMLTag *vcal_item_get_xml(Folder *folder, FolderItem *item)
282 XMLTag *tag;
284 tag = folder_item_get_xml(folder, item);
286 if (((VCalFolderItem *)item)->uri)
287 xml_tag_add_attr(tag, xml_attr_new("uri", ((VCalFolderItem *)item)->uri));
289 xml_tag_add_attr(tag, xml_attr_new_int("use_cal_view", ((VCalFolderItem *)item)->use_cal_view));
291 return tag;
294 static gint vcal_rename_folder(Folder *folder, FolderItem *item,
295 const gchar *name)
297 if (!name)
298 return -1;
299 g_free(item->name);
300 item->name = g_strdup(name);
301 return 0;
304 static void vcal_get_sort_type(Folder *folder, FolderSortKey *sort_key,
305 FolderSortType *sort_type)
307 if (sort_key)
308 *sort_key = SORT_BY_DATE;
311 static void vcal_item_opened(FolderItem *item)
313 struct tm tmdate;
314 time_t t = time(NULL);
315 #ifndef G_OS_WIN32
316 localtime_r(&t, &tmdate);
317 #else
318 if (t < 0)
319 t = 1;
320 tmdate = *localtime(&t);
321 #endif
322 if (!((VCalFolderItem *)(item))->dw
323 && ((VCalFolderItem *)(item))->use_cal_view == 1)
324 ((VCalFolderItem *)(item))->dw = create_day_win(item, tmdate);
325 else if (!((VCalFolderItem *)(item))->mw
326 && ((VCalFolderItem *)(item))->use_cal_view == 2)
327 ((VCalFolderItem *)(item))->mw = create_month_win(item, tmdate);
328 else if (((VCalFolderItem *)(item))->use_cal_view != 0)
329 vcal_folder_refresh_cal(item);
332 void vcal_folder_refresh_cal(FolderItem *item)
334 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
335 if (item->folder != folder)
336 return;
337 if (((VCalFolderItem *)(item))->dw)
338 refresh_day_win(((VCalFolderItem *)(item))->dw);
339 if (((VCalFolderItem *)(item))->mw)
340 refresh_month_win(((VCalFolderItem *)(item))->mw);
343 static void vcal_item_closed(FolderItem *item)
345 if (((VCalFolderItem *)(item))->dw) {
346 dw_close_window(((VCalFolderItem *)(item))->dw);
347 ((VCalFolderItem *)(item))->dw = NULL;
349 if (((VCalFolderItem *)(item))->mw) {
350 mw_close_window(((VCalFolderItem *)(item))->mw);
351 ((VCalFolderItem *)(item))->mw = NULL;
355 FolderClass *vcal_folder_get_class()
357 if (vcal_class.idstr == NULL) {
358 debug_print("register class\n");
359 vcal_class.type = F_UNKNOWN;
360 vcal_class.idstr = PLUGIN_NAME;
361 vcal_class.uistr = PLUGIN_NAME;
363 /* Folder functions */
364 vcal_class.new_folder = vcal_folder_new;
365 vcal_class.destroy_folder = vcal_folder_destroy;
366 vcal_class.set_xml = folder_set_xml;
367 vcal_class.get_xml = folder_get_xml;
368 vcal_class.item_set_xml = vcal_item_set_xml;
369 vcal_class.item_get_xml = vcal_item_get_xml;
370 vcal_class.scan_tree = vcal_scan_tree;
371 vcal_class.create_tree = vcal_create_tree;
372 vcal_class.get_sort_type = vcal_get_sort_type;
374 /* FolderItem functions */
375 vcal_class.item_new = vcal_item_new;
376 vcal_class.item_destroy = vcal_item_destroy;
377 vcal_class.item_get_path = vcal_item_get_path;
378 vcal_class.create_folder = vcal_create_folder;
379 vcal_class.remove_folder = vcal_remove_folder;
380 vcal_class.rename_folder = vcal_rename_folder;
381 vcal_class.scan_required = vcal_scan_required;
382 vcal_class.set_mtime = vcal_set_mtime;
383 vcal_class.get_num_list = vcal_get_num_list;
384 vcal_class.set_batch = vcal_folder_set_batch;
386 /* Message functions */
387 vcal_class.get_msginfo = vcal_get_msginfo;
388 vcal_class.fetch_msg = vcal_fetch_msg;
389 vcal_class.add_msg = vcal_add_msg;
390 vcal_class.copy_msg = NULL;
391 vcal_class.remove_msg = vcal_remove_msg;
392 vcal_class.change_flags = vcal_change_flags;
393 vcal_class.subscribe = vcal_subscribe_uri;
395 /* FolderView functions */
396 vcal_class.item_opened = vcal_item_opened;
397 vcal_class.item_closed = vcal_item_closed;
398 debug_print("registered class for real\n");
401 return &vcal_class;
404 static void vcal_folder_set_batch (Folder *folder,
405 FolderItem *_item,
406 gboolean batch)
408 VCalFolderItem *item = (VCalFolderItem *)_item;
410 g_return_if_fail(item != NULL);
412 if (item->batching == batch)
413 return;
415 if (batch) {
416 item->batching = TRUE;
417 debug_print("vcal switching to batch mode\n");
418 } else {
419 debug_print("vcal switching away from batch mode\n");
420 /* ici */
421 item->batching = FALSE;
422 if (item->dirty)
423 vcal_folder_export(folder);
424 item->dirty = FALSE;
428 static Folder *vcal_folder_new(const gchar * name,
429 const gchar * path)
431 VCalFolder *folder;
433 debug_print("vcal_folder_new\n");
434 folder = g_new0(VCalFolder, 1);
435 FOLDER(folder)->klass = &vcal_class;
436 folder_init(FOLDER(folder), name);
438 return FOLDER(folder);
441 static void vcal_folder_destroy(Folder *_folder)
445 static FolderItem *vcal_item_new(Folder *folder)
447 VCalFolderItem *item;
448 item = g_new0(VCalFolderItem, 1);
449 item->use_cal_view = 1;
450 return (FolderItem *) item;
454 static void vcal_item_destroy(Folder *folder, FolderItem *_item)
456 VCalFolderItem *item = (VCalFolderItem *)_item;
457 g_return_if_fail(item != NULL);
458 g_free(item);
461 static gchar *vcal_item_get_path(Folder *folder, FolderItem *item)
463 VCalFolderItem *fitem = (VCalFolderItem *)item;
464 if (fitem->uri == NULL)
465 return g_strdup(vcal_manager_get_event_path());
466 else {
467 gchar *path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
468 "vcalendar", G_DIR_SEPARATOR_S,
469 item->path, NULL);
470 return path;
474 static gint vcal_scan_tree(Folder *folder)
476 g_return_val_if_fail(folder != NULL, -1);
478 folder->outbox = NULL;
479 folder->draft = NULL;
480 folder->queue = NULL;
481 folder->trash = NULL;
483 debug_print("scanning tree\n");
484 vcal_create_tree(folder);
486 return 0;
489 gboolean manual_update = TRUE;
491 static gint feed_fetch(FolderItem *fitem, MsgNumberList ** list, gboolean *old_uids_valid)
493 VCalFolderItem *item = (VCalFolderItem *)fitem;
494 icalcomponent *evt = NULL;
495 icalcomponent_kind type = ICAL_VEVENT_COMPONENT;
496 gint num = 1;
497 gint past_msg = -1, today_msg = -1, tomorrow_msg = -1,
498 thisweek_msg = -1, later_msg = -1;
500 debug_print("fetching\n");
502 if (!item->uri) {
503 debug_print("no uri!\n");
504 return -1;
507 update_subscription(item->uri, TRUE);
509 *old_uids_valid = FALSE;
510 *list = NULL;
512 if (item->cal) {
513 evt = icalcomponent_get_first_component(
514 item->cal, ICAL_VEVENT_COMPONENT);
515 if (evt == NULL) {
516 evt = icalcomponent_get_first_component(
517 item->cal, ICAL_VTODO_COMPONENT);
518 if (evt != NULL)
519 type = ICAL_VTODO_COMPONENT;
521 } else
522 debug_print("no cal!\n");
524 if (evt == NULL)
525 debug_print("no event\n");
527 if (item->numlist) {
528 g_slist_free(item->numlist);
529 item->numlist = NULL;
532 if (item->evtlist) {
533 slist_free_icalfeeddata(item->evtlist);
534 g_slist_free(item->evtlist);
535 item->evtlist = NULL;
538 while (evt) {
539 icalproperty *prop;
540 icalproperty *rprop = icalcomponent_get_first_property(evt, ICAL_RRULE_PROPERTY);
541 struct icalrecurrencetype recur;
542 struct icaltimetype next;
543 icalrecur_iterator* ritr = NULL;
544 EventTime days;
546 if (rprop) {
547 icalproperty *rprop2;
548 recur = icalproperty_get_rrule(rprop);
549 rprop2 = icalproperty_new_rrule(recur);
550 prop = icalcomponent_get_first_property(evt, ICAL_DTSTART_PROPERTY);
551 if (prop) {
552 ritr = icalrecur_iterator_new(recur, icalproperty_get_dtstart(prop));
553 next = icalrecur_iterator_next(ritr); /* skip first one */
556 rprop = rprop2;
559 prop = icalcomponent_get_first_property(evt, ICAL_UID_PROPERTY);
560 if (prop) {
561 gchar *orig_uid = NULL;
562 gchar *uid = g_strdup(icalproperty_get_uid(prop));
563 IcalFeedData *data = icalfeeddata_new(
564 icalcomponent_new_clone(evt), NULL);
565 int i = 0;
566 orig_uid = g_strdup(uid);
568 add_new:
569 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
570 item->evtlist = g_slist_prepend(item->evtlist, data);
571 data = NULL;
572 debug_print("add %d : %s\n", num, uid);
573 g_free(uid);
574 num++;
575 prop = icalcomponent_get_first_property(evt, ICAL_DTSTART_PROPERTY);
576 if (prop) {
577 struct icaltimetype itt = icalproperty_get_dtstart(prop);
578 days = event_to_today(NULL, icaltime_as_timet(itt));
579 if (days == EVENT_PAST && past_msg == -1) {
580 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
581 data = icalfeeddata_new(NULL, EVENT_PAST_ID);
582 past_msg = num++;
583 } else if (days == EVENT_TODAY && today_msg == -1) {
584 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
585 data = icalfeeddata_new(NULL, EVENT_TODAY_ID);
586 today_msg = num++;
587 } else if (days == EVENT_TOMORROW && tomorrow_msg == -1) {
588 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
589 data = icalfeeddata_new(NULL, EVENT_TOMORROW_ID);
590 tomorrow_msg = num++;
591 } else if (days == EVENT_THISWEEK && thisweek_msg == -1) {
592 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
593 data = icalfeeddata_new(NULL, EVENT_THISWEEK_ID);
594 thisweek_msg = num++;
595 } else if (days == EVENT_LATER && later_msg == -1) {
596 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
597 data = icalfeeddata_new(NULL, EVENT_LATER_ID);
598 later_msg = num++;
600 } else {
601 if (past_msg == -1) {
602 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
603 data = icalfeeddata_new(NULL, EVENT_PAST_ID);
604 past_msg = num++;
607 if (data) {
608 item->evtlist = g_slist_prepend(item->evtlist, data);
609 data = NULL;
611 if (rprop && ritr) {
612 struct icaldurationtype ical_dur;
613 struct icaltimetype dtstart = icaltime_null_time();
614 struct icaltimetype dtend = icaltime_null_time();
615 evt = icalcomponent_new_clone(evt);
616 prop = icalcomponent_get_first_property(evt, ICAL_RRULE_PROPERTY);
617 if (prop) {
618 icalcomponent_remove_property(evt, prop);
619 icalproperty_free(prop);
621 prop = icalcomponent_get_first_property(evt, ICAL_DTSTART_PROPERTY);
622 if (prop)
623 dtstart = icalproperty_get_dtstart(prop);
624 else
625 debug_print("event has no DTSTART!\n");
626 prop = icalcomponent_get_first_property(evt, ICAL_DTEND_PROPERTY);
627 if (prop)
628 dtend = icalproperty_get_dtend(prop);
629 else
630 debug_print("event has no DTEND!\n");
631 ical_dur = icaltime_subtract(dtend, dtstart);
632 next = icalrecur_iterator_next(ritr);
633 if (!icaltime_is_null_time(next) &&
634 !icaltime_is_null_time(dtstart) && i < 100) {
635 prop = icalcomponent_get_first_property(evt, ICAL_DTSTART_PROPERTY);
636 icalproperty_set_dtstart(prop, next);
638 prop = icalcomponent_get_first_property(evt, ICAL_DTEND_PROPERTY);
639 if (prop)
640 icalproperty_set_dtend(prop, icaltime_add(next, ical_dur));
642 prop = icalcomponent_get_first_property(evt, ICAL_UID_PROPERTY);
643 uid = g_strdup_printf("%s-%d", orig_uid, i);
644 icalproperty_set_uid(prop, uid);
645 /* dont free uid, used after (add_new) */
646 data = icalfeeddata_new(evt, NULL);
647 i++;
648 goto add_new;
649 } else {
650 icalcomponent_free(evt);
651 evt = NULL;
654 g_free(orig_uid);
655 } else {
656 debug_print("no uid!\n");
658 if (rprop) {
659 icalproperty_free(rprop);
661 if (ritr) {
662 icalrecur_iterator_free(ritr);
663 ritr = NULL;
665 evt = icalcomponent_get_next_component(
666 item->cal, type);
668 if (today_msg == -1) {
669 IcalFeedData *data = icalfeeddata_new(NULL, EVENT_TODAY_ID);
670 item->numlist = g_slist_prepend(item->numlist, GINT_TO_POINTER(num));
671 today_msg = num++;
672 item->evtlist = g_slist_prepend(item->evtlist, data);
674 item->numlist = g_slist_reverse(item->numlist);
675 item->evtlist = g_slist_reverse(item->evtlist);
677 *list = item->numlist ? g_slist_copy(item->numlist) : NULL;
678 debug_print("return %d\n", num);
679 return num;
682 #define VCAL_FOLDER_ADD_EVENT(event) \
685 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
686 debug_print("add %d %s\n", n_msg, event->uid); \
687 n_msg++; \
688 days = event_to_today(event, 0); \
690 if (days == EVENT_PAST && past_msg == -1) { \
691 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
692 past_msg = n_msg++; \
693 g_hash_table_insert(hash_uids, GINT_TO_POINTER(past_msg), g_strdup(EVENT_PAST_ID)); \
694 } else if (days == EVENT_TODAY && today_msg == -1) { \
695 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
696 today_msg = n_msg++; \
697 g_hash_table_insert(hash_uids, GINT_TO_POINTER(today_msg), g_strdup(EVENT_TODAY_ID)); \
698 } else if (days == EVENT_TOMORROW && tomorrow_msg == -1) { \
699 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
700 tomorrow_msg = n_msg++; \
701 g_hash_table_insert(hash_uids, GINT_TO_POINTER(tomorrow_msg), g_strdup(EVENT_TOMORROW_ID)); \
702 } else if (days == EVENT_THISWEEK && thisweek_msg == -1) { \
703 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
704 thisweek_msg = n_msg++; \
705 g_hash_table_insert(hash_uids, GINT_TO_POINTER(thisweek_msg), g_strdup(EVENT_THISWEEK_ID)); \
706 } else if (days == EVENT_LATER && later_msg == -1) { \
707 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg)); \
708 later_msg = n_msg++; \
709 g_hash_table_insert(hash_uids, GINT_TO_POINTER(later_msg), g_strdup(EVENT_LATER_ID)); \
713 GSList *vcal_get_events_list(FolderItem *item)
715 GDir *dp;
716 const gchar *d;
717 GSList *events = NULL;
718 GError *error = NULL;
720 if (item != item->folder->inbox) {
721 GSList *subs = vcal_folder_get_webcal_events_for_folder(item);
722 GSList *cur = NULL;
723 for (cur = subs; cur; cur = cur->next) {
724 /* Don't free that, it's done when subscriptions are
725 * fetched */
726 icalcomponent *ical = (icalcomponent *)cur->data;
727 VCalEvent *event = vcal_get_event_from_ical(
728 icalcomponent_as_ical_string(ical), NULL);
729 events = g_slist_prepend(events, event);
731 g_slist_free(subs);
732 return events;
735 dp = g_dir_open(vcal_manager_get_event_path(), 0, &error);
737 if (!dp) {
738 debug_print("couldn't open dir '%s': %s (%d)\n",
739 vcal_manager_get_event_path(), error->message, error->code);
740 g_error_free(error);
741 return 0;
744 while ((d = g_dir_read_name(dp)) != NULL) {
745 VCalEvent *event = NULL;
746 if (d[0] == '.' || strstr(d, ".bak")
747 || !strcmp(d, "internal.ics")
748 || !strcmp(d, "internal.ifb")
749 || !strcmp(d, "multisync"))
750 continue;
752 event = vcal_manager_load_event(d);
753 if (!event)
754 continue;
755 if (event->rec_occurrence) {
756 vcal_manager_free_event(event);
757 claws_unlink(d);
758 continue;
761 if (event && event->method != ICAL_METHOD_CANCEL) {
762 PrefsAccount *account = vcal_manager_get_account_from_event(event);
763 enum icalparameter_partstat status =
764 account ? vcal_manager_get_reply_for_attendee(event, account->address): ICAL_PARTSTAT_NEEDSACTION;
765 if (status == ICAL_PARTSTAT_ACCEPTED
766 || status == ICAL_PARTSTAT_TENTATIVE) {
767 events = g_slist_prepend(events, event);
768 } else {
769 vcal_manager_free_event(event);
770 continue;
772 if ((status == ICAL_PARTSTAT_ACCEPTED
773 || status == ICAL_PARTSTAT_TENTATIVE)
774 && event->recur && *(event->recur)) {
775 struct icalrecurrencetype recur;
776 struct icaltimetype dtstart;
777 struct icaltimetype next;
778 icalrecur_iterator* ritr;
779 time_t duration = (time_t) NULL;
780 struct icaldurationtype ical_dur;
781 int i = 0;
783 debug_print("dumping recurring events from main event %s\n", d);
784 recur = icalrecurrencetype_from_string(event->recur);
785 dtstart = icaltime_from_string(event->dtstart);
787 duration = icaltime_as_timet(icaltime_from_string(event->dtend))
788 - icaltime_as_timet(icaltime_from_string(event->dtstart));
790 ical_dur = icaldurationtype_from_int(duration);
792 ritr = icalrecur_iterator_new(recur, dtstart);
794 next = icalrecur_iterator_next(ritr); /* skip first one */
795 if (!icaltime_is_null_time(next))
796 next = icalrecur_iterator_next(ritr);
797 debug_print("next time is %snull\n", icaltime_is_null_time(next)?"":"not ");
798 while (!icaltime_is_null_time(next) && i < 100) {
799 const gchar *new_start = NULL, *new_end = NULL;
800 VCalEvent *nevent = NULL;
801 gchar *uid = g_strdup_printf("%s-%d", event->uid, i);
802 new_start = icaltime_as_ical_string(next);
803 new_end = icaltime_as_ical_string(
804 icaltime_add(next, ical_dur));
805 debug_print("adding with start/end %s:%s\n", new_start, new_end);
806 nevent = vcal_manager_new_event(uid, event->organizer, event->orgname,
807 event->location, event->summary, event->description,
808 new_start, new_end, NULL,
809 event->tzid, event->url, event->method,
810 event->sequence, event->type);
811 g_free(uid);
812 vcal_manager_copy_attendees(event, nevent);
813 nevent->rec_occurrence = TRUE;
814 vcal_manager_save_event(nevent, FALSE);
815 account = vcal_manager_get_account_from_event(event);
816 status =
817 account ? vcal_manager_get_reply_for_attendee(event, account->address): ICAL_PARTSTAT_NEEDSACTION;
818 if (status == ICAL_PARTSTAT_ACCEPTED
819 || status == ICAL_PARTSTAT_TENTATIVE) {
820 events = g_slist_prepend(events, nevent);
821 } else {
822 vcal_manager_free_event(nevent);
824 next = icalrecur_iterator_next(ritr);
825 debug_print("next time is %snull\n", icaltime_is_null_time(next)?"":"not ");
826 i++;
828 icalrecur_iterator_free(ritr);
830 } else {
831 vcal_manager_free_event(event);
834 g_dir_close(dp);
835 return g_slist_reverse(events);
838 static gint vcal_get_num_list(Folder *folder, FolderItem *item,
839 MsgNumberList ** list, gboolean *old_uids_valid)
841 int n_msg = 1;
842 gint past_msg = -1, today_msg = -1, tomorrow_msg = -1,
843 thisweek_msg = -1, later_msg = -1;
844 GSList *events = NULL, *cur;
845 START_TIMING("");
846 g_return_val_if_fail (*list == NULL, 0); /* we expect a NULL list */
848 debug_print(" num for %s\n", ((VCalFolderItem *)item)->uri ? ((VCalFolderItem *)item)->uri:"(null)");
850 *old_uids_valid = FALSE;
852 if (((VCalFolderItem *)item)->uri)
853 return feed_fetch(item, list, old_uids_valid);
855 events = vcal_get_events_list(item);
857 debug_print("get_num_list\n");
859 if (hash_uids != NULL)
860 g_hash_table_destroy(hash_uids);
862 hash_uids = g_hash_table_new_full(g_direct_hash, g_direct_equal,
863 NULL, g_free);
865 for (cur = events; cur; cur = cur->next) {
866 VCalEvent *event = (VCalEvent *)cur->data;
868 if (!event)
869 continue;
870 g_hash_table_insert(hash_uids, GINT_TO_POINTER(n_msg), g_strdup(event->uid));
872 if (event->rec_occurrence) {
873 vcal_manager_free_event(event);
874 continue;
877 if (event->method != ICAL_METHOD_CANCEL) {
878 EventTime days;
879 VCAL_FOLDER_ADD_EVENT(event);
881 if (event)
882 vcal_manager_free_event(event);
887 if (today_msg == -1) {
888 *list = g_slist_prepend(*list, GINT_TO_POINTER(n_msg));
889 today_msg = n_msg++;
890 g_hash_table_insert(hash_uids, GINT_TO_POINTER(today_msg), g_strdup(EVENT_TODAY_ID));
893 g_slist_free(events);
894 vcal_folder_export(folder);
896 vcal_set_mtime(folder, item);
898 *list = g_slist_reverse(*list);
899 END_TIMING();
900 return g_slist_length(*list);
903 static MsgInfo *vcal_parse_msg(const gchar *file, FolderItem *item, int num)
905 MsgInfo *msginfo = NULL;
906 MsgFlags flags;
908 debug_print("parse_msg\n");
910 flags.perm_flags = 0;
911 flags.tmp_flags = 0;
912 msginfo = procheader_parse_file(file, flags, TRUE, TRUE);
914 msginfo->msgnum = num;
915 msginfo->folder = item;
916 return msginfo;
919 static MsgInfo *vcal_get_msginfo(Folder * folder,
920 FolderItem * item, gint num)
922 MsgInfo *msginfo = NULL;
923 gchar *file;
925 debug_print("get_msginfo\n");
927 g_return_val_if_fail(item != NULL, NULL);
928 g_return_val_if_fail(num > 0, NULL);
930 file = vcal_fetch_msg(folder, item, num);
932 if (!file) {
933 return NULL;
936 msginfo = vcal_parse_msg(file, item, num);
938 if (msginfo) {
939 msginfo->flags.perm_flags = 0;
940 msginfo->flags.tmp_flags = 0;
942 vcal_change_flags(NULL, NULL, msginfo, 0);
944 debug_print(" adding %d\n", num);
946 g_unlink(file);
947 g_free(file);
949 debug_print(" got msginfo %p\n", msginfo);
951 return msginfo;
954 static gchar *feed_fetch_item(FolderItem * fitem, gint num)
956 gchar *filename = NULL;
957 VCalFolderItem *item = (VCalFolderItem *)fitem;
958 GSList *ncur, *ecur;
959 int i = 1;
960 IcalFeedData *data = NULL;
962 if (!item->numlist) {
963 folder_item_scan_full(fitem, FALSE);
965 if (!item->numlist) {
966 debug_print("numlist null\n");
967 return NULL;
970 ncur = item->numlist;
971 ecur = item->evtlist;
973 while (i < num) {
974 if (!ncur || !ecur) {
975 debug_print("list short end (%d to %d) %d,%d\n", i, num, ncur!=NULL, ecur!=NULL);
976 return NULL;
978 ncur = ncur->next;
979 ecur = ecur->next;
980 i++;
983 data = (IcalFeedData *)ecur->data;
985 if (!data) {
986 return NULL;
989 if (data->event)
990 filename = vcal_manager_icalevent_dump(data->event, fitem->name, NULL);
991 else if (data->pseudoevent_id) {
992 filename = vcal_manager_dateevent_dump(data->pseudoevent_id, fitem);
993 created_files = g_slist_prepend(created_files, g_strdup(filename));
994 } else {
995 debug_print("no event\n");
996 return NULL;
999 debug_print("feed item dump to %s\n", filename);
1000 return filename;
1003 static gchar *vcal_fetch_msg(Folder * folder, FolderItem * item,
1004 gint num)
1006 gchar *filename = NULL;
1007 const gchar *uid = NULL;
1009 debug_print(" fetch for %s %d\n", (((VCalFolderItem *)item)->uri ? ((VCalFolderItem *)item)->uri:"(null)"), num);
1010 if (((VCalFolderItem *)item)->uri)
1011 return feed_fetch_item(item, num);
1013 if (!uid) {
1014 if (!hash_uids)
1015 folder_item_scan_full(item, FALSE);
1016 uid = g_hash_table_lookup(hash_uids, GINT_TO_POINTER(num));
1018 if (uid &&
1019 (!strcmp(uid, EVENT_PAST_ID) ||
1020 !strcmp(uid, EVENT_TODAY_ID) ||
1021 !strcmp(uid, EVENT_TOMORROW_ID) ||
1022 !strcmp(uid, EVENT_THISWEEK_ID) ||
1023 !strcmp(uid, EVENT_LATER_ID))) {
1024 filename = vcal_manager_dateevent_dump(uid, item);
1025 } else if (uid) {
1026 VCalEvent *event = NULL;
1027 event = vcal_manager_load_event(uid);
1028 if (event)
1029 filename = vcal_manager_event_dump(event, FALSE, TRUE, NULL, FALSE);
1031 if (filename) {
1032 created_files = g_slist_prepend(created_files, g_strdup(filename));
1035 vcal_manager_free_event(event);
1038 return filename;
1041 static gint vcal_add_msg(Folder *folder, FolderItem *_dest, const gchar *file, MsgFlags *flags)
1043 gchar *contents = file_read_to_str(file);
1044 if (contents) {
1045 vcal_add_event(contents);
1047 g_free(contents);
1048 return 0;
1051 static void vcal_remove_event (Folder *folder, MsgInfo *msginfo);
1053 static gint vcal_remove_msg(Folder *folder, FolderItem *_item, gint num)
1055 MsgInfo *msginfo = folder_item_get_msginfo(_item, num);
1057 if (!msginfo)
1058 return 0;
1060 if (_item == folder->inbox)
1061 vcal_remove_event(folder, msginfo);
1063 procmsg_msginfo_free(&msginfo);
1064 return 0;
1067 static FolderItem *vcal_create_folder(Folder * folder,
1068 FolderItem * parent,
1069 const gchar * name)
1071 gchar *path = NULL;
1072 FolderItem *newitem = NULL;
1073 debug_print("creating new vcal folder\n");
1075 path = g_strconcat((parent->path != NULL) ? parent->path : "", ".", name, NULL);
1076 newitem = folder_item_new(folder, name, path);
1077 folder_item_append(parent, newitem);
1078 g_free(path);
1080 return newitem;
1083 static gint vcal_create_tree(Folder *folder)
1085 FolderItem *rootitem, *inboxitem;
1086 GNode *rootnode, *inboxnode;
1088 if (!folder->node) {
1089 rootitem = folder_item_new(folder, folder->name, NULL);
1090 rootitem->folder = folder;
1091 rootnode = g_node_new(rootitem);
1092 folder->node = rootnode;
1093 rootitem->node = rootnode;
1094 } else {
1095 rootitem = FOLDER_ITEM(folder->node->data);
1096 rootnode = folder->node;
1099 /* Add inbox folder */
1100 if (!folder->inbox) {
1101 inboxitem = folder_item_new(folder, _("Meetings"), ".meetings");
1102 inboxitem->folder = folder;
1103 inboxitem->stype = F_INBOX;
1104 inboxnode = g_node_new(inboxitem);
1105 inboxitem->node = inboxnode;
1106 folder->inbox = inboxitem;
1107 g_node_append(rootnode, inboxnode);
1108 } else {
1109 g_free(folder->inbox->path);
1110 folder->inbox->path = g_strdup(".meetings");
1113 debug_print("created new vcal tree\n");
1114 return 0;
1117 static gint vcal_remove_folder(Folder *folder, FolderItem *fitem)
1119 VCalFolderItem *item = (VCalFolderItem *)fitem;
1120 if (!item->uri)
1121 return -1;
1122 else {
1123 if (item->feed)
1124 g_free(item->feed);
1125 if (item->uri)
1126 g_free(item->uri);
1127 item->feed = NULL;
1128 item->uri = NULL;
1129 folder_item_remove(fitem);
1130 return 0;
1134 static gboolean vcal_scan_required(Folder *folder, FolderItem *item)
1136 GStatBuf s;
1137 VCalFolderItem *vitem = (VCalFolderItem *)item;
1139 g_return_val_if_fail(item != NULL, FALSE);
1141 if (vitem->uri) {
1142 return TRUE;
1143 } else if (g_stat(vcal_manager_get_event_path(), &s) < 0) {
1144 return TRUE;
1145 } else if ((s.st_mtime > item->mtime) &&
1146 (s.st_mtime - 3600 != item->mtime)) {
1147 return TRUE;
1149 return FALSE;
1152 static gint vcal_folder_lock_count = 0;
1154 static void vcal_set_mtime(Folder *folder, FolderItem *item)
1156 GStatBuf s;
1157 gchar *path = folder_item_get_path(item);
1159 if (folder->inbox != item)
1160 return;
1162 g_return_if_fail(path != NULL);
1164 if (g_stat(path, &s) < 0) {
1165 FILE_OP_ERROR(path, "stat");
1166 g_free(path);
1167 return;
1170 item->mtime = s.st_mtime;
1171 debug_print("VCAL: forced mtime of %s to %"CM_TIME_FORMAT"\n",
1172 item->name?item->name:"(null)", item->mtime);
1173 g_free(path);
1176 void vcal_folder_export(Folder *folder)
1178 FolderItem *item = folder?folder->inbox:NULL;
1179 gboolean need_scan = folder?vcal_scan_required(folder, item):TRUE;
1180 gchar *export_pass = NULL;
1181 gchar *export_freebusy_pass = NULL;
1183 if (vcal_folder_lock_count) /* blocked */
1184 return;
1185 vcal_folder_lock_count++;
1187 export_pass = vcal_passwd_get("export");
1188 export_freebusy_pass = vcal_passwd_get("export_freebusy");
1190 if (vcal_meeting_export_calendar(vcalprefs.export_path,
1191 vcalprefs.export_user,
1192 export_pass,
1193 TRUE)) {
1194 debug_print("exporting calendar\n");
1195 if (vcalprefs.export_enable &&
1196 vcalprefs.export_command &&
1197 strlen(vcalprefs.export_command))
1198 execute_command_line(
1199 vcalprefs.export_command, TRUE, NULL);
1201 if (export_pass != NULL) {
1202 memset(export_pass, 0, strlen(export_pass));
1204 g_free(export_pass);
1205 if (vcal_meeting_export_freebusy(vcalprefs.export_freebusy_path,
1206 vcalprefs.export_freebusy_user,
1207 export_freebusy_pass)) {
1208 debug_print("exporting freebusy\n");
1209 if (vcalprefs.export_freebusy_enable &&
1210 vcalprefs.export_freebusy_command &&
1211 strlen(vcalprefs.export_freebusy_command))
1212 execute_command_line(
1213 vcalprefs.export_freebusy_command, TRUE, NULL);
1215 if (export_freebusy_pass != NULL) {
1216 memset(export_freebusy_pass, 0, strlen(export_freebusy_pass));
1218 g_free(export_freebusy_pass);
1219 vcal_folder_lock_count--;
1220 if (!need_scan && folder) {
1221 vcal_set_mtime(folder, folder->inbox);
1225 static void vcal_remove_event (Folder *folder, MsgInfo *msginfo)
1227 const gchar *uid = msginfo->msgid;
1228 VCalFolderItem *item = (VCalFolderItem *)msginfo->folder;
1230 if (uid) {
1231 gchar *file = vcal_manager_get_event_file(uid);
1232 g_unlink(file);
1233 g_free(file);
1236 if (!item || !item->batching)
1237 vcal_folder_export(folder);
1238 else if (item) {
1239 item->dirty = TRUE;
1243 static void vcal_change_flags(Folder *folder, FolderItem *_item, MsgInfo *msginfo, MsgPermFlags newflags)
1245 EventTime date;
1247 if (newflags & MSG_DELETED) {
1248 /* delete the stuff */
1249 msginfo->flags.perm_flags |= MSG_DELETED;
1250 vcal_remove_event(folder, msginfo);
1251 return;
1254 /* accept the rest */
1255 msginfo->flags.perm_flags = newflags;
1257 /* but not color */
1258 msginfo->flags.perm_flags &= ~MSG_CLABEL_FLAG_MASK;
1260 date = event_to_today(NULL, msginfo->date_t);
1261 switch(date) {
1262 case EVENT_PAST:
1263 break;
1264 case EVENT_TODAY:
1265 msginfo->flags.perm_flags |= MSG_COLORLABEL_TO_FLAGS(2); /* Red */
1266 break;
1267 case EVENT_TOMORROW:
1268 break;
1269 case EVENT_THISWEEK:
1270 break;
1271 case EVENT_LATER:
1272 break;
1274 if (msginfo->msgid) {
1275 if (!strcmp(msginfo->msgid, EVENT_TODAY_ID) ||
1276 !strcmp(msginfo->msgid, EVENT_TOMORROW_ID))
1277 msginfo->flags.perm_flags |= MSG_MARKED;
1281 void vcal_folder_gtk_init(void)
1283 vcal_fill_popup_menu_labels();
1285 folderview_register_popup(&vcal_popup);
1288 void vcal_folder_gtk_done(void)
1290 GSList *cur = created_files;
1291 while (cur) {
1292 gchar *file = (gchar *)cur->data;
1293 cur = cur->next;
1294 if (!file)
1295 continue;
1296 debug_print("removing %s\n", file);
1297 if (g_unlink(file) < 0)
1298 FILE_OP_ERROR(file, "g_unlink");
1299 g_free(file);
1301 g_slist_free(created_files);
1302 folderview_unregister_popup(&vcal_popup);
1305 static void add_menuitems(GtkUIManager *ui_manager, FolderItem *item)
1307 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "NewMeeting", "FolderViewPopup/NewMeeting", GTK_UI_MANAGER_MENUITEM)
1308 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "ExportCal", "FolderViewPopup/ExportCal", GTK_UI_MANAGER_MENUITEM)
1309 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVcal1", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
1310 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SubscribeCal", "FolderViewPopup/SubscribeCal", GTK_UI_MANAGER_MENUITEM)
1311 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "UnsubscribeCal", "FolderViewPopup/UnsubscribeCal", GTK_UI_MANAGER_MENUITEM)
1312 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVcal2", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
1313 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "RenameFolder", "FolderViewPopup/RenameFolder", GTK_UI_MANAGER_MENUITEM)
1314 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVcal3", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
1315 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "CheckSubs", "FolderViewPopup/CheckSubs", GTK_UI_MANAGER_MENUITEM)
1316 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVcal4", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
1317 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "ListView", "FolderViewPopup/ListView", GTK_UI_MANAGER_MENUITEM)
1318 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "WeekView", "FolderViewPopup/WeekView", GTK_UI_MANAGER_MENUITEM)
1319 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "MonthView", "FolderViewPopup/MonthView", GTK_UI_MANAGER_MENUITEM)
1320 MENUITEM_ADDUI_MANAGER(ui_manager, "/Popup/FolderViewPopup", "SeparatorVcal5", "FolderViewPopup/---", GTK_UI_MANAGER_SEPARATOR)
1323 static gboolean setting_sensitivity = FALSE;
1324 static void set_sensitivity(GtkUIManager *ui_manager, FolderItem *fitem)
1326 VCalFolderItem *item = (VCalFolderItem *)fitem;
1328 #define SET_SENS(name, sens) \
1329 cm_menu_set_sensitive_full(ui_manager, "Popup/"name, sens)
1331 setting_sensitivity = TRUE;
1333 cm_toggle_menu_set_active_full(ui_manager, "Popup/FolderViewPopup/ListView", (item->use_cal_view == 0));
1334 cm_toggle_menu_set_active_full(ui_manager, "Popup/FolderViewPopup/WeekView", (item->use_cal_view == 1));
1335 cm_toggle_menu_set_active_full(ui_manager, "Popup/FolderViewPopup/MonthView", (item->use_cal_view == 2));
1336 SET_SENS("FolderViewPopup/NewMeeting", item->uri == NULL);
1337 SET_SENS("FolderViewPopup/ExportCal", TRUE);
1338 SET_SENS("FolderViewPopup/SubscribeCal", item->uri == NULL);
1339 SET_SENS("FolderViewPopup/UnsubscribeCal", item->uri != NULL);
1340 SET_SENS("FolderViewPopup/RenameFolder", folder_item_parent(fitem) != NULL);
1341 SET_SENS("FolderViewPopup/CheckSubs", TRUE);
1342 SET_SENS("FolderViewPopup/ListView", folder_item_parent(fitem) != NULL);
1343 SET_SENS("FolderViewPopup/WeekView", folder_item_parent(fitem) != NULL);
1344 SET_SENS("FolderViewPopup/MonthView", folder_item_parent(fitem) != NULL);
1345 setting_sensitivity = FALSE;
1346 #undef SET_SENS
1349 static void new_meeting_cb(GtkAction *action, gpointer data)
1351 debug_print("new_meeting_cb\n");
1352 vcal_meeting_create(NULL);
1355 GSList * vcal_folder_get_waiting_events(void)
1357 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1358 return vcal_get_events_list(folder->inbox);
1361 typedef struct _get_webcal_data {
1362 GSList *list;
1363 FolderItem *item;
1364 } GetWebcalData;
1366 static gboolean get_webcal_events_func(GNode *node, gpointer user_data)
1368 FolderItem *item = node->data;
1369 GetWebcalData *data = user_data;
1370 gboolean dummy = FALSE;
1371 GSList *list = NULL, *cur = NULL;
1373 if (data->item && data->item != item)
1374 return FALSE;
1376 feed_fetch(item, &list, &dummy);
1378 g_slist_free(list);
1380 for (cur = ((VCalFolderItem *)item)->evtlist; cur; cur = cur->next) {
1381 IcalFeedData *fdata = (IcalFeedData *)cur->data;
1382 if (fdata->event)
1383 data->list = g_slist_prepend(data->list, fdata->event);
1385 return FALSE;
1388 GSList * vcal_folder_get_webcal_events(void)
1390 GetWebcalData *data = g_new0(GetWebcalData, 1);
1391 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1392 GSList *list = NULL;
1393 data->item = NULL;
1394 g_node_traverse(folder->node, G_PRE_ORDER,
1395 G_TRAVERSE_ALL, -1, get_webcal_events_func, data);
1397 list = data->list;
1398 g_free(data);
1400 return g_slist_reverse(list);
1403 static gboolean vcal_free_data_func(GNode *node, gpointer user_data)
1405 VCalFolderItem *item = node->data;
1407 if (item->cal) {
1408 icalcomponent_free(item->cal);
1409 item->cal = NULL;
1411 if (item->numlist) {
1412 g_slist_free(item->numlist);
1413 item->numlist = NULL;
1416 if (item->evtlist) {
1417 slist_free_icalfeeddata(item->evtlist);
1418 g_slist_free(item->evtlist);
1419 item->evtlist = NULL;
1422 return FALSE;
1425 void vcal_folder_free_data(void)
1427 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1429 g_node_traverse(folder->node, G_PRE_ORDER,
1430 G_TRAVERSE_ALL, -1, vcal_free_data_func, NULL);
1433 GSList * vcal_folder_get_webcal_events_for_folder(FolderItem *item)
1435 GetWebcalData *data = g_new0(GetWebcalData, 1);
1436 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1437 GSList *list = NULL;
1438 data->item = item;
1439 g_node_traverse(folder->node, G_PRE_ORDER,
1440 G_TRAVERSE_ALL, -1, get_webcal_events_func, data);
1442 list = data->list;
1443 g_free(data);
1445 return g_slist_reverse(list);
1448 gchar* get_item_event_list_for_date(FolderItem *item, EventTime date)
1450 GSList *strs = NULL;
1451 GSList *cur;
1452 gchar *result = NULL;
1453 gchar *datestr = NULL;
1455 if (((VCalFolderItem *)item)->uri) {
1456 for (cur = ((VCalFolderItem *)item)->evtlist; cur; cur = cur->next) {
1457 IcalFeedData *fdata = (IcalFeedData *)cur->data;
1458 icalproperty *prop;
1459 struct icaltimetype itt;
1460 gchar *summary = NULL;
1461 EventTime days;
1462 if (!fdata->event)
1463 continue;
1464 prop = icalcomponent_get_first_property((icalcomponent *)fdata->event, ICAL_DTSTART_PROPERTY);
1466 if (!prop)
1467 continue;
1468 itt = icalproperty_get_dtstart(prop);
1469 days = event_to_today(NULL, icaltime_as_timet(itt));
1470 if (days != date)
1471 continue;
1472 prop = icalcomponent_get_first_property((icalcomponent *)fdata->event, ICAL_SUMMARY_PROPERTY);
1473 if (prop) {
1474 if (!g_utf8_validate(icalproperty_get_summary(prop), -1, NULL))
1475 summary = conv_codeset_strdup(icalproperty_get_summary(prop),
1476 conv_get_locale_charset_str(), CS_UTF_8);
1477 else
1478 summary = g_strdup(icalproperty_get_summary(prop));
1479 } else
1480 summary = g_strdup("-");
1482 strs = g_slist_prepend(strs, summary);
1484 } else {
1485 GSList *evtlist = vcal_folder_get_waiting_events();
1486 for (cur = evtlist; cur; cur = cur->next) {
1487 VCalEvent *event = (VCalEvent *)cur->data;
1488 EventTime days;
1489 days = event_to_today(event, 0);
1490 gchar *summary = NULL;
1491 if (days == date) {
1492 summary = g_strdup(event->summary);
1493 strs = g_slist_prepend(strs, summary);
1495 vcal_manager_free_event(event);
1499 switch(date) {
1500 case EVENT_PAST:
1501 datestr=_("in the past");
1502 break;
1503 case EVENT_TODAY:
1504 datestr=_("today");
1505 break;
1506 case EVENT_TOMORROW:
1507 datestr=_("tomorrow");
1508 break;
1509 case EVENT_THISWEEK:
1510 datestr=_("this week");
1511 break;
1512 case EVENT_LATER:
1513 datestr=_("later");
1514 break;
1517 result = g_strdup_printf(_("\nThese are the events planned %s:\n"),
1518 datestr?datestr:"never");
1520 strs = g_slist_reverse(strs);
1521 for (cur = strs; cur; cur = cur->next) {
1522 int e_len = strlen(result);
1523 int n_len = strlen((gchar *)cur->data);
1524 if (e_len) {
1525 result = g_realloc(result, e_len+n_len+4);
1526 *(result+e_len) = '\n';
1527 strcpy(result+e_len+1, "- ");
1528 strcpy(result+e_len+3, (gchar *)cur->data);
1529 } else {
1530 result = g_realloc(result, e_len+n_len+3);
1531 strcpy(result+e_len, "- ");
1532 strcpy(result+e_len+2, (gchar *)cur->data);
1535 slist_free_strings_full(strs);
1536 return result;
1539 static void export_cal_cb(GtkAction *action, gpointer data)
1541 vcal_meeting_export_calendar(NULL, NULL, NULL, FALSE);
1544 struct CBuf {
1545 gchar *str;
1548 static size_t curl_recv(void *buf, size_t size, size_t nmemb, void *stream)
1550 struct CBuf *buffer = (struct CBuf *)stream;
1551 gchar *tmp = NULL;
1552 gchar *tmpbuf = g_malloc0(size*nmemb + 1);
1554 g_return_val_if_fail(tmpbuf != NULL, 0);
1556 memcpy(tmpbuf, buf, size*nmemb);
1558 if (buffer->str) {
1559 /* If the buffer already has contents, append the new data. */
1560 tmp = g_strconcat(buffer->str, tmpbuf, NULL);
1561 g_free(tmpbuf);
1562 g_free(buffer->str);
1563 buffer->str = tmp;
1564 } else {
1565 buffer->str = tmpbuf;
1568 return size*nmemb;
1571 void *url_read_thread(void *data)
1573 thread_data *td = (thread_data *)data;
1574 CURLcode res;
1575 CURL *curl_ctx = NULL;
1576 long response_code;
1577 struct CBuf buffer = { NULL };
1578 gchar *t_url = (gchar *)td->url;
1580 while (*t_url == ' ')
1581 t_url++;
1582 if (strchr(t_url, ' '))
1583 *(strchr(t_url, ' ')) = '\0';
1585 #ifdef USE_PTHREAD
1586 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
1587 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
1588 #endif
1590 curl_ctx = curl_easy_init();
1592 curl_easy_setopt(curl_ctx, CURLOPT_URL, t_url);
1593 curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, curl_recv);
1594 curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, &buffer);
1595 curl_easy_setopt(curl_ctx, CURLOPT_TIMEOUT, prefs_common_get_prefs()->io_timeout_secs);
1596 curl_easy_setopt(curl_ctx, CURLOPT_NOSIGNAL, 1);
1597 #ifdef G_OS_WIN32
1598 curl_easy_setopt(curl_ctx, CURLOPT_CAINFO, claws_ssl_get_cert_file());
1599 #endif
1600 #if LIBCURL_VERSION_NUM >= 0x070a00
1601 if(vcalprefs.ssl_verify_peer == FALSE) {
1602 curl_easy_setopt(curl_ctx, CURLOPT_SSL_VERIFYPEER, 0);
1603 curl_easy_setopt(curl_ctx, CURLOPT_SSL_VERIFYHOST, 0);
1605 #endif
1606 curl_easy_setopt(curl_ctx, CURLOPT_USERAGENT,
1607 "Claws Mail vCalendar plugin "
1608 "(" PLUGINS_URI ")");
1609 curl_easy_setopt(curl_ctx, CURLOPT_FOLLOWLOCATION, 1);
1610 res = curl_easy_perform(curl_ctx);
1612 if (res != 0) {
1613 debug_print("res %d %s\n", res, curl_easy_strerror(res));
1614 td->error = g_strdup(curl_easy_strerror(res));
1616 if(res == CURLE_OPERATION_TIMEOUTED)
1617 log_error(LOG_PROTOCOL, _("Timeout (%d seconds) connecting to %s\n"),
1618 prefs_common_get_prefs()->io_timeout_secs, t_url);
1621 curl_easy_getinfo(curl_ctx, CURLINFO_RESPONSE_CODE, &response_code);
1622 if( response_code >= 400 && response_code < 500 ) {
1623 debug_print("VCalendar: got %ld\n", response_code);
1624 switch(response_code) {
1625 case 401:
1626 td->error = g_strdup(_("401 (Authorisation required)"));
1627 break;
1628 case 403:
1629 td->error = g_strdup(_("403 (Unauthorised)"));
1630 break;
1631 case 404:
1632 td->error = g_strdup(_("404 (Not found)"));
1633 break;
1634 default:
1635 td->error = g_strdup_printf(_("Error %ld"), response_code);
1636 break;
1639 curl_easy_cleanup(curl_ctx);
1640 if (buffer.str) {
1641 td->result = g_strdup(buffer.str);
1642 g_free(buffer.str);
1645 td->done = TRUE; /* let the caller thread join() */
1646 return GINT_TO_POINTER(0);
1649 gchar *vcal_curl_read(const char *url, const gchar *label, gboolean verbose,
1650 void (*callback)(const gchar *url, gchar *data, gboolean verbose, gchar *error))
1652 gchar *result;
1653 thread_data *td;
1654 #ifdef USE_PTHREAD
1655 pthread_t pt;
1656 #endif
1657 void *res;
1658 gchar *error = NULL;
1659 result = NULL;
1660 td = g_new0(thread_data, 1);
1661 res = NULL;
1663 td->url = url;
1664 td->result = NULL;
1665 td->done = FALSE;
1667 STATUSBAR_PUSH(mainwindow_get_mainwindow(), label);
1669 #ifdef USE_PTHREAD
1670 if (pthread_create(&pt, NULL, url_read_thread, td) != 0) {
1671 url_read_thread(td);
1673 while (!td->done) {
1674 claws_do_idle();
1677 pthread_join(pt, &res);
1678 #else
1679 url_read_thread(td);
1680 #endif
1682 result = td->result;
1683 error = td->error;
1684 g_free(td);
1686 STATUSBAR_POP(mainwindow_get_mainwindow());
1688 if (callback) {
1689 callback(url, result, verbose, error);
1690 return NULL;
1691 } else {
1692 if (error)
1693 g_free(error);
1694 return result;
1698 gboolean vcal_curl_put(gchar *url, FILE *fp, gint filesize, const gchar *user, const gchar *pass)
1700 gboolean res = TRUE;
1701 CURL *curl_ctx = curl_easy_init();
1702 long response_code = 0;
1703 gchar *t_url = url;
1704 gchar *userpwd = NULL;
1706 struct curl_slist * headers = curl_slist_append(NULL,
1707 "Content-Type: text/calendar; charset=\"utf-8\"" );
1709 while (*t_url == ' ')
1710 t_url++;
1711 if (strchr(t_url, ' '))
1712 *(strchr(t_url, ' ')) = '\0';
1714 if (user && pass && *user && *pass) {
1715 userpwd = g_strdup_printf("%s:%s",user,pass);
1716 curl_easy_setopt(curl_ctx, CURLOPT_USERPWD, userpwd);
1718 curl_easy_setopt(curl_ctx, CURLOPT_URL, t_url);
1719 curl_easy_setopt(curl_ctx, CURLOPT_UPLOAD, 1);
1720 curl_easy_setopt(curl_ctx, CURLOPT_READFUNCTION, NULL);
1721 curl_easy_setopt(curl_ctx, CURLOPT_READDATA, fp);
1722 curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, headers);
1723 #ifdef G_OS_WIN32
1724 curl_easy_setopt(curl_ctx, CURLOPT_CAINFO, claws_ssl_get_cert_file());
1725 #endif
1726 #if LIBCURL_VERSION_NUM >= 0x070a00
1727 if(vcalprefs.ssl_verify_peer == FALSE) {
1728 curl_easy_setopt(curl_ctx, CURLOPT_SSL_VERIFYPEER, 0);
1729 curl_easy_setopt(curl_ctx, CURLOPT_SSL_VERIFYHOST, 0);
1731 #endif
1732 curl_easy_setopt(curl_ctx, CURLOPT_USERAGENT,
1733 "Claws Mail vCalendar plugin "
1734 "(" PLUGINS_URI ")");
1735 curl_easy_setopt(curl_ctx, CURLOPT_INFILESIZE, filesize);
1736 res = curl_easy_perform(curl_ctx);
1737 g_free(userpwd);
1739 if (res != 0) {
1740 debug_print("res %d %s\n", res, curl_easy_strerror(res));
1741 } else {
1742 res = TRUE;
1745 curl_easy_getinfo(curl_ctx, CURLINFO_RESPONSE_CODE, &response_code);
1746 if (response_code < 200 || response_code >= 300) {
1747 g_warning("can't export calendar, got code %ld", response_code);
1748 res = FALSE;
1750 curl_easy_cleanup(curl_ctx);
1751 curl_slist_free_all(headers);
1752 return res;
1755 static gboolean folder_item_find_func(GNode *node, gpointer data)
1757 FolderItem *item = node->data;
1758 gpointer *d = data;
1759 const gchar *uri = d[0];
1761 if (!uri || !((VCalFolderItem *)item)->uri
1762 || strcmp(uri, ((VCalFolderItem *)item)->uri))
1763 return FALSE;
1765 d[1] = item;
1767 return TRUE;
1770 static FolderItem *get_folder_item_for_uri(const gchar *uri)
1772 Folder *root = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1773 gpointer d[2];
1775 if (root == NULL)
1776 return NULL;
1778 d[0] = (gpointer)uri;
1779 d[1] = NULL;
1780 g_node_traverse(root->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1781 folder_item_find_func, d);
1782 return d[1];
1785 static gchar *feed_get_title(const gchar *str)
1787 gchar *title = NULL;
1788 if (strstr(str, "X-WR-CALNAME:")) {
1789 title = g_strdup(strstr(str, "X-WR-CALNAME:")+strlen("X-WR-CALNAME:"));
1790 } else if (strstr(str, "X-WR-CALDESC:")) {
1791 title = g_strdup(strstr(str, "X-WR-CALDESC:")+strlen("X-WR-CALDESC:"));
1793 return strcrlftrunc(title);
1796 static void update_subscription_finish(const gchar *uri, gchar *feed, gboolean verbose, gchar *error)
1798 Folder *root = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1799 FolderItem *item = NULL;
1800 icalcomponent *cal = NULL;
1802 if (root == NULL) {
1803 g_warning("can't get root folder");
1804 g_free(feed);
1805 if (error)
1806 g_free(error);
1807 return;
1810 if (feed == NULL) {
1811 gchar *err_msg = _("Could not retrieve the Webcal URL:\n%s:\n\n%s");
1813 if (verbose && manual_update) {
1814 gchar *tmp = g_strdup(uri);
1815 if (strlen(uri) > 61) {
1816 tmp[55]='[';
1817 tmp[56]='.';
1818 tmp[57]='.';
1819 tmp[58]='.';
1820 tmp[59]=']';
1821 tmp[60]='\0';
1823 alertpanel_error(err_msg, tmp, error ? error:_("Unknown error"));
1824 g_free(tmp);
1825 } else {
1826 gchar *msg = g_strdup_printf("%s\n", err_msg);
1827 log_error(LOG_PROTOCOL, msg, uri, error ? error:_("Unknown error"));
1828 g_free(msg);
1830 main_window_cursor_normal(mainwindow_get_mainwindow());
1831 g_free(feed);
1832 if (error)
1833 g_free(error);
1834 return;
1837 gchar *tmp = feed;
1838 while (*tmp && isspace((unsigned char)*tmp))
1839 tmp++;
1841 if (strncmp(tmp, "BEGIN:VCALENDAR", strlen("BEGIN:VCALENDAR"))) {
1842 gchar *err_msg = _("This URL does not look like a Webcal URL:\n%s\n%s");
1844 if (verbose && manual_update) {
1845 alertpanel_error(err_msg, uri, error ? error:_("Unknown error"));
1846 } else {
1847 gchar *msg = g_strdup_printf("%s\n", err_msg);
1848 log_error(LOG_PROTOCOL, msg, uri, error ? error:_("Unknown error"));
1849 g_free(msg);
1851 g_free(feed);
1852 main_window_cursor_normal(mainwindow_get_mainwindow());
1853 if (error)
1854 g_free(error);
1855 return;
1858 if (error)
1859 g_free(error);
1860 item = get_folder_item_for_uri(uri);
1861 if (item == NULL) {
1862 gchar *title = feed_get_title(feed);
1863 if (title == NULL) {
1864 if (strstr(uri, "://"))
1865 title = g_path_get_basename(strstr(uri,"://")+3);
1866 else
1867 title = g_strdup(uri);
1868 subst_for_filename(title);
1870 item = folder_create_folder(root->node->data, title);
1871 if (!item) {
1872 if (verbose && manual_update) {
1873 alertpanel_error(_("Could not create directory %s"),
1874 title);
1875 } else {
1876 log_error(LOG_PROTOCOL, _("Could not create directory %s"),
1877 title);
1879 g_free(feed);
1880 g_free(title);
1881 main_window_cursor_normal(mainwindow_get_mainwindow());
1882 return;
1884 debug_print("item done %s\n", title);
1885 ((VCalFolderItem *)item)->uri = g_strdup(uri);
1886 ((VCalFolderItem *)item)->feed = feed;
1887 g_free(title);
1888 } else {
1889 if (((VCalFolderItem *)item)->feed)
1890 g_free(((VCalFolderItem *)item)->feed);
1892 ((VCalFolderItem *)item)->feed = feed;
1893 /* if title differs, update it */
1895 cal = icalparser_parse_string(feed);
1897 convert_to_utc(cal);
1899 if (((VCalFolderItem *)item)->cal)
1900 icalcomponent_free(((VCalFolderItem *)item)->cal);
1902 ((VCalFolderItem *)item)->cal = cal;
1904 main_window_cursor_normal(mainwindow_get_mainwindow());
1905 ((VCalFolderItem *)item)->last_fetch = time(NULL);
1908 static void update_subscription(const gchar *uri, gboolean verbose)
1910 FolderItem *item = get_folder_item_for_uri(uri);
1911 gchar *label;
1913 if (prefs_common_get_prefs()->work_offline) {
1914 if (!verbose ||
1915 !inc_offline_should_override(TRUE,
1916 _("Claws Mail needs network access in order "
1917 "to update the Webcal feed.")))
1918 return;
1920 if (item) {
1921 if (time(NULL) - ((VCalFolderItem *)(item))->last_fetch < 60 &&
1922 ((VCalFolderItem *)(item))->cal)
1923 return;
1925 main_window_cursor_wait(mainwindow_get_mainwindow());
1927 label = g_strdup_printf(_("Fetching calendar for %s..."),
1928 item && item->name ? item->name : _("new subscription"));
1929 vcal_curl_read(uri, label, verbose, update_subscription_finish);
1930 g_free(label);
1933 static void check_subs_cb(GtkAction *action, gpointer data)
1935 Folder *root = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
1937 if (prefs_common_get_prefs()->work_offline &&
1938 !inc_offline_should_override(TRUE,
1939 _("Claws Mail needs network access in order "
1940 "to update the subscription.")))
1941 return;
1943 folderview_check_new(root);
1946 static void subscribe_cal_cb(GtkAction *action, gpointer data)
1948 gchar *uri = NULL;
1949 gchar *tmp = NULL;
1950 gchar *clip_text = NULL, *str = NULL;
1952 clip_text = gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
1954 if (clip_text) {
1955 str = clip_text;
1956 #if GLIB_CHECK_VERSION(2,66,0)
1957 GError *error = NULL;
1958 GUri *uri = NULL;
1960 /* skip any leading white-space */
1961 while (str && *str && g_ascii_isspace(*str))
1962 str++;
1963 uri = g_uri_parse(str, G_URI_FLAGS_PARSE_RELAXED, &error);
1964 if (error) {
1965 g_warning("could not parse clipboard text for URI: '%s'", error->message);
1966 g_error_free(error);
1968 if (uri) {
1969 gchar* newstr = g_uri_to_string(uri);
1971 debug_print("URI: '%s' -> '%s'\n", str, newstr ? newstr : "N/A");
1972 if (newstr)
1973 g_free(newstr);
1974 g_uri_unref(uri);
1975 } else {
1976 #else
1977 if (!is_uri_string(str)) {
1978 #endif
1979 /* if no URL, ignore clipboard text */
1980 str = NULL;
1984 tmp = input_dialog(_("Subscribe to Webcal"), _("Enter the Webcal URL:"), str ? str : "");
1986 if (clip_text)
1987 g_free(clip_text);
1989 if (tmp == NULL)
1990 return;
1992 if (!strncmp(tmp, "http", 4)) {
1993 uri = tmp;
1994 } else if (!strncmp(tmp, "file://", 7)) {
1995 uri = tmp;
1996 } else if (!strncmp(tmp, "webcal", 6)) {
1997 uri = g_strconcat("http", tmp+6, NULL);
1998 g_free(tmp);
1999 } else {
2000 alertpanel_error(_("Could not parse the URL."));
2001 g_free(tmp);
2002 return;
2004 debug_print("uri %s\n", uri);
2006 update_subscription(uri, TRUE);
2007 folder_write_list();
2008 g_free(uri);
2011 static void unsubscribe_cal_cb(GtkAction *action, gpointer data)
2013 FolderView *folderview = (FolderView *)data;
2014 FolderItem *item, *opened;
2015 gchar *message;
2016 AlertValue avalue;
2017 gchar *old_id;
2019 if (!folderview->selected) return;
2021 item = folderview_get_selected_item(folderview);
2022 g_return_if_fail(item != NULL);
2023 g_return_if_fail(item->path != NULL);
2024 g_return_if_fail(item->folder != NULL);
2025 opened = folderview_get_opened_item(folderview);
2027 message = g_strdup_printf
2028 (_("Do you really want to unsubscribe?"));
2029 avalue = alertpanel_full(_("Delete subscription"), message,
2030 NULL, _("_Cancel"), "edit-delete", _("_Delete"),
2031 NULL, NULL, ALERTFOCUS_FIRST,
2032 FALSE, NULL, ALERT_WARNING);
2033 g_free(message);
2034 if (avalue != G_ALERTALTERNATE) return;
2036 old_id = folder_item_get_identifier(item);
2038 vcal_item_closed(item);
2040 if (item == opened ||
2041 folder_is_child_of(item, opened)) {
2042 summary_clear_all(folderview->summaryview);
2043 folderview_close_opened(folderview, TRUE);
2046 if (item->folder->klass->remove_folder(item->folder, item) < 0) {
2047 folder_item_scan(item);
2048 alertpanel_error(_("Can't remove the folder '%s'."), item->name);
2049 g_free(old_id);
2050 return;
2053 folder_write_list();
2055 prefs_filtering_delete_path(old_id);
2056 g_free(old_id);
2059 gboolean vcal_subscribe_uri(Folder *folder, const gchar *uri)
2061 gchar *tmp = NULL;
2062 if (folder->klass != vcal_folder_get_class())
2063 return FALSE;
2065 if (uri == NULL)
2066 return FALSE;
2068 if (!strncmp(uri, "webcal", 6)) {
2069 tmp = g_strconcat("http", uri+6, NULL);
2070 } else {
2071 return FALSE;
2073 debug_print("uri %s\n", tmp);
2075 update_subscription(tmp, FALSE);
2076 folder_write_list();
2077 return TRUE;
2080 static void rename_cb(GtkAction *action, gpointer data)
2082 FolderView *folderview = (FolderView *)data;
2083 FolderItem *item;
2084 gchar *new_folder;
2085 gchar *name;
2086 gchar *message;
2088 item = folderview_get_selected_item(folderview);
2089 g_return_if_fail(item != NULL);
2090 g_return_if_fail(item->path != NULL);
2091 g_return_if_fail(item->folder != NULL);
2093 name = trim_string(item->name, 32);
2094 message = g_strdup_printf(_("Input new name for '%s':"), name);
2095 new_folder = input_dialog(_("Rename folder"), message, name);
2096 g_free(message);
2097 g_free(name);
2098 if (!new_folder) return;
2099 AUTORELEASE_STR(new_folder, {g_free(new_folder); return;});
2101 if (strchr(new_folder, G_DIR_SEPARATOR) != NULL) {
2102 alertpanel_error(_("'%c' can't be included in folder name."),
2103 G_DIR_SEPARATOR);
2104 return;
2107 if (folder_find_child_item_by_name(folder_item_parent(item), new_folder)) {
2108 name = trim_string(new_folder, 32);
2109 alertpanel_error(_("The folder '%s' already exists."), name);
2110 g_free(name);
2111 return;
2114 if (folder_item_rename(item, new_folder) < 0) {
2115 alertpanel_error(_("The folder could not be renamed.\n"
2116 "The new folder name is not allowed."));
2117 return;
2120 folder_item_prefs_save_config_recursive(item);
2121 folder_write_list();
2124 static void set_view_cb(GtkAction *gaction, GtkRadioAction *current, gpointer data)
2126 FolderView *folderview = (FolderView *)data;
2127 gint action = gtk_radio_action_get_current_value (GTK_RADIO_ACTION (current));
2128 FolderItem *item = NULL, *oitem = NULL;
2130 if (!folderview->selected) return;
2131 if (setting_sensitivity) return;
2133 oitem = folderview_get_opened_item(folderview);
2134 item = folderview_get_selected_item(folderview);
2136 if (!item)
2137 return;
2139 if (((VCalFolderItem *)(item))->use_cal_view == action)
2140 return;
2141 debug_print("set view %d\n", action);
2142 if (oitem && item == oitem && oitem->folder->klass == vcal_folder_get_class())
2143 oitem->folder->klass->item_closed(oitem);
2144 ((VCalFolderItem *)(item))->use_cal_view = action;
2145 if (((VCalFolderItem *)(item))->use_cal_view) {
2146 if (oitem && item == oitem && oitem->folder->klass == vcal_folder_get_class())
2147 oitem->folder->klass->item_opened(oitem);
2151 gchar *vcal_get_event_as_ical_str(VCalEvent *event)
2153 gchar *ical;
2154 icalcomponent *calendar = icalcomponent_vanew(
2155 ICAL_VCALENDAR_COMPONENT,
2156 icalproperty_new_version("2.0"),
2157 icalproperty_new_prodid(
2158 "-//Claws Mail//NONSGML Claws Mail Calendar//EN"),
2159 icalproperty_new_calscale("GREGORIAN"),
2160 (void*)0);
2161 vcal_manager_event_dump(event, FALSE, FALSE, calendar, FALSE);
2162 ical = g_strdup(icalcomponent_as_ical_string(calendar));
2163 icalcomponent_free(calendar);
2165 return ical;
2168 static gchar *get_name_from_property(icalproperty *p)
2170 gchar *tmp = NULL;
2172 if (p && icalproperty_get_parameter_as_string(p, "CN") != NULL)
2173 tmp = g_strdup(icalproperty_get_parameter_as_string(p, "CN"));
2175 return tmp;
2178 static gchar *get_email_from_property(icalproperty *p)
2180 gchar *tmp = NULL;
2181 gchar *email = NULL;
2183 if (p)
2184 tmp = g_strdup(icalproperty_get_organizer(p));
2186 if (!tmp)
2187 return NULL;
2189 if (!strncasecmp(tmp, "MAILTO:", strlen("MAILTO:")))
2190 email = g_strdup(tmp+strlen("MAILTO:"));
2191 else
2192 email = g_strdup(tmp);
2193 g_free(tmp);
2195 return email;
2198 static void convert_to_utc(icalcomponent *calendar)
2200 icalcomponent *event;
2201 icaltimezone *tz, *tzutc = icaltimezone_get_utc_timezone();
2202 icalproperty *prop;
2203 icalparameter *tzid;
2205 cm_return_if_fail(calendar != NULL);
2207 for (
2208 event = icalcomponent_get_first_component(calendar,
2209 ICAL_VEVENT_COMPONENT);
2210 event != NULL;
2211 event = icalcomponent_get_next_component(calendar,
2212 ICAL_VEVENT_COMPONENT)) {
2214 /* DTSTART */
2215 if ((prop = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY)) != NULL
2216 && (tzid = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER)) != NULL) {
2217 /* Event has its DTSTART with a timezone specification, let's convert
2218 * to UTC and remove the TZID parameter. */
2220 tz = icalcomponent_get_timezone(calendar, icalparameter_get_iana_value(tzid));
2221 if (tz != NULL) {
2222 debug_print("Converting DTSTART to UTC.\n");
2223 icaltimetype t = icalproperty_get_dtstart(prop);
2224 icaltimezone_convert_time(&t, tz, tzutc);
2225 icalproperty_set_dtstart(prop, t);
2226 icalproperty_remove_parameter_by_ref(prop, tzid);
2230 /* DTEND */
2231 if ((prop = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY)) != NULL
2232 && (tzid = icalproperty_get_first_parameter(prop, ICAL_TZID_PARAMETER)) != NULL) {
2233 /* Event has its DTEND with a timezone specification, let's convert
2234 * to UTC and remove the TZID parameter. */
2236 tz = icalcomponent_get_timezone(calendar, icalparameter_get_iana_value(tzid));
2237 if (tz != NULL) {
2238 debug_print("Converting DTEND to UTC.\n");
2239 icaltimetype t = icalproperty_get_dtend(prop);
2240 icaltimezone_convert_time(&t, tz, tzutc);
2241 icalproperty_set_dtend(prop, t);
2242 icalproperty_remove_parameter_by_ref(prop, tzid);
2248 #define GET_PROP(comp,prop,kind) { \
2249 prop = NULL; \
2250 if (!(prop = icalcomponent_get_first_property(comp, kind))) { \
2251 prop = inner \
2252 ? icalcomponent_get_first_property(inner, kind) \
2253 : NULL; \
2257 #define GET_PROP_LIST(comp,list,kind) { \
2258 list = NULL; \
2259 if (!(prop = icalcomponent_get_first_property(comp, kind))) { \
2260 prop = inner \
2261 ? icalcomponent_get_first_property(inner, kind) \
2262 : NULL; \
2263 if (prop) do { \
2264 list = g_slist_prepend(list, prop); \
2265 } while ((prop = icalcomponent_get_next_property(inner, kind)));\
2266 }else do { \
2267 list = g_slist_prepend(list, prop); \
2268 } while ((prop = icalcomponent_get_next_property(comp, kind))); \
2271 #define TO_UTF8(string) { \
2272 if (string && !g_utf8_validate(string, -1, NULL)) { \
2273 gchar *tmp = conv_codeset_strdup(string, \
2274 charset ? charset:conv_get_locale_charset_str(),\
2275 CS_UTF_8); \
2276 g_free(string); \
2277 string = tmp; \
2281 VCalEvent *vcal_get_event_from_ical(const gchar *ical, const gchar *charset)
2283 VCalEvent *event = NULL;
2284 gchar *int_ical = g_strdup(ical);
2285 icalcomponent *comp = icalcomponent_new_from_string(int_ical);
2286 icalcomponent *inner = NULL;
2287 icalproperty *prop = NULL;
2288 GSList *list = NULL, *cur = NULL;
2289 gchar *uid = NULL;
2290 gchar *location = NULL;
2291 gchar *summary = NULL;
2292 gchar *dtstart = NULL;
2293 gchar *dtend = NULL;
2294 gchar *org_email = NULL, *org_name = NULL;
2295 gchar *description = NULL;
2296 gchar *url = NULL;
2297 gchar *tzid = NULL;
2298 gchar *recur = NULL;
2299 int sequence = 0;
2300 enum icalproperty_method method = ICAL_METHOD_REQUEST;
2301 enum icalcomponent_kind type = ICAL_VEVENT_COMPONENT;
2302 GSList *attendees = NULL;
2304 if (comp == NULL) {
2305 g_free(int_ical);
2306 return NULL;
2309 if ((inner = icalcomponent_get_inner(comp)) != NULL)
2310 type = icalcomponent_isa(inner);
2312 GET_PROP(comp, prop, ICAL_UID_PROPERTY);
2313 if (prop) {
2314 uid = g_strdup(icalproperty_get_uid(prop));
2315 TO_UTF8(uid);
2316 icalproperty_free(prop);
2318 GET_PROP(comp, prop, ICAL_LOCATION_PROPERTY);
2319 if (prop) {
2320 location = g_strdup(icalproperty_get_location(prop));
2321 TO_UTF8(location);
2322 icalproperty_free(prop);
2324 GET_PROP(comp, prop, ICAL_SUMMARY_PROPERTY);
2325 if (prop) {
2326 summary = g_strdup(icalproperty_get_summary(prop));
2327 TO_UTF8(summary);
2328 icalproperty_free(prop);
2331 convert_to_utc(comp);
2333 GET_PROP(comp, prop, ICAL_DTSTART_PROPERTY);
2334 if (prop) {
2335 dtstart = g_strdup(icaltime_as_ical_string(icalproperty_get_dtstart(prop)));
2336 TO_UTF8(dtstart);
2337 icalproperty_free(prop);
2339 GET_PROP(comp, prop, ICAL_DTEND_PROPERTY);
2340 if (prop) {
2341 dtend = g_strdup(icaltime_as_ical_string(icalproperty_get_dtend(prop)));
2342 TO_UTF8(dtend);
2343 icalproperty_free(prop);
2344 } else {
2345 GET_PROP(comp, prop, ICAL_DURATION_PROPERTY);
2346 if (prop) {
2347 struct icaldurationtype duration = icalproperty_get_duration(prop);
2348 struct icaltimetype itt;
2349 icalproperty_free(prop);
2350 GET_PROP(comp, prop, ICAL_DTSTART_PROPERTY);
2351 if (prop) {
2352 itt = icalproperty_get_dtstart(prop);
2353 icalproperty_free(prop);
2354 dtend = g_strdup(icaltime_as_ical_string(icaltime_add(itt,duration)));
2355 TO_UTF8(dtend);
2359 GET_PROP(comp, prop, ICAL_SEQUENCE_PROPERTY);
2360 if (prop) {
2361 sequence = icalproperty_get_sequence(prop);
2362 icalproperty_free(prop);
2364 GET_PROP(comp, prop, ICAL_METHOD_PROPERTY);
2365 if (prop) {
2366 method = icalproperty_get_method(prop);
2367 icalproperty_free(prop);
2369 GET_PROP(comp, prop, ICAL_ORGANIZER_PROPERTY);
2370 if (prop) {
2371 org_email = get_email_from_property(prop);
2372 TO_UTF8(org_email);
2373 org_name = get_name_from_property(prop);
2374 TO_UTF8(org_name);
2375 icalproperty_free(prop);
2377 GET_PROP(comp, prop, ICAL_DESCRIPTION_PROPERTY);
2378 if (prop) {
2379 description = g_strdup(icalproperty_get_description(prop));
2380 TO_UTF8(description);
2381 icalproperty_free(prop);
2383 GET_PROP(comp, prop, ICAL_URL_PROPERTY);
2384 if (prop) {
2385 url = g_strdup(icalproperty_get_url(prop));
2386 TO_UTF8(url);
2387 icalproperty_free(prop);
2389 GET_PROP(comp, prop, ICAL_TZID_PROPERTY);
2390 if (prop) {
2391 tzid = g_strdup(icalproperty_get_tzid(prop));
2392 TO_UTF8(tzid);
2393 icalproperty_free(prop);
2395 GET_PROP(comp, prop, ICAL_RRULE_PROPERTY);
2396 if (prop) {
2397 struct icalrecurrencetype rrule = icalproperty_get_rrule(prop);
2398 recur = g_strdup(icalrecurrencetype_as_string(&rrule));
2399 TO_UTF8(recur);
2400 icalproperty_free(prop);
2402 GET_PROP_LIST(comp, list, ICAL_ATTENDEE_PROPERTY);
2403 for (cur = list; cur; cur = cur->next) {
2404 enum icalparameter_partstat partstat = 0;
2405 enum icalparameter_cutype cutype = 0;
2406 icalparameter *param = NULL;
2407 gchar *email = NULL;
2408 gchar *name = NULL;
2409 Answer *answer = NULL;
2411 prop = (icalproperty *)(cur->data);
2413 email = get_email_from_property(prop);
2414 TO_UTF8(email);
2415 name = get_name_from_property(prop);
2416 TO_UTF8(name);
2418 param = icalproperty_get_first_parameter(prop, ICAL_PARTSTAT_PARAMETER);
2419 if (param)
2420 partstat = icalparameter_get_partstat(param);
2422 param = icalproperty_get_first_parameter(prop, ICAL_CUTYPE_PARAMETER);
2423 if (param)
2424 cutype= icalparameter_get_cutype(param);
2426 if (!partstat)
2427 partstat = ICAL_PARTSTAT_NEEDSACTION;
2428 if (!cutype)
2429 cutype = ICAL_CUTYPE_INDIVIDUAL;
2430 answer = answer_new(email, name, partstat, cutype);
2431 attendees = g_slist_prepend(attendees, answer);
2432 g_free(email);
2433 g_free(name);
2434 icalproperty_free(prop);
2436 g_slist_free(list);
2438 event = vcal_manager_new_event (uid, org_email, org_name,
2439 location, summary, description,
2440 dtstart, dtend, recur,
2441 tzid, url,
2442 method, sequence, type);
2443 event->answers = attendees;
2444 g_free(uid);
2445 g_free(location);
2446 g_free(summary);
2447 g_free(dtstart);
2448 g_free(dtend);
2449 g_free(org_email);
2450 g_free(org_name);
2451 g_free(description);
2452 g_free(url);
2453 g_free(tzid);
2454 g_free(recur);
2455 g_free(int_ical);
2456 icalcomponent_free(comp);
2457 return event;
2460 gboolean vcal_event_exists(const gchar *id)
2462 MsgInfo *info = NULL;
2463 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
2464 if (!folder)
2465 return FALSE;
2467 info = folder_item_get_msginfo_by_msgid(folder->inbox, id);
2468 if (info != NULL) {
2469 procmsg_msginfo_free(&info);
2470 return TRUE;
2472 return FALSE;
2475 void vcal_foreach_event(gboolean (*cb_func)(const gchar *vevent))
2477 GSList *list = vcal_folder_get_waiting_events();
2478 GSList *cur = NULL;
2479 if (!cb_func)
2480 return;
2481 debug_print("calling cb_func...\n");
2482 for (cur = list; cur; cur = cur->next) {
2483 VCalEvent *event = (VCalEvent *)cur->data;
2484 gchar *tmp = vcal_get_event_as_ical_str(event);
2485 if (tmp) {
2486 debug_print(" ...for event %s\n", event->uid);
2487 cb_func(tmp);
2489 vcal_manager_free_event(event);
2490 g_free(tmp);
2494 /* please call vcalendar_refresh_folder_contents() after one or more
2495 * calls to this function */
2496 gboolean vcal_delete_event(const gchar *id)
2498 MsgInfo *info = NULL;
2499 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
2500 if (!folder)
2501 return FALSE;
2503 info = folder_item_get_msginfo_by_msgid(folder->inbox, id);
2504 if (info != NULL) {
2505 debug_print("removing event %s\n", id);
2506 vcal_remove_event(folder, info);
2507 procmsg_msginfo_free(&info);
2508 folder_item_scan(folder->inbox);
2509 return TRUE;
2511 debug_print("not removing unexisting event %s\n", id);
2512 return FALSE;
2515 /* please call vcalendar_refresh_folder_contents() after one or more
2516 * calls to this function */
2517 gchar* vcal_add_event(const gchar *vevent)
2519 VCalEvent *event = vcal_get_event_from_ical(vevent, NULL);
2520 gchar *retVal = NULL;
2521 Folder *folder = folder_find_from_name (PLUGIN_NAME, vcal_folder_get_class());
2522 if (!folder) {
2523 vcal_manager_free_event(event);
2524 return NULL;
2527 if (event) {
2528 if (vcal_event_exists(event->uid)) {
2529 debug_print("event %s already exists\n", event->uid);
2530 vcal_manager_free_event(event);
2531 return retVal;
2533 debug_print("adding event %s\n", event->uid);
2534 if (!account_find_from_address(event->organizer, FALSE) &&
2535 !vcal_manager_get_account_from_event(event)) {
2536 PrefsAccount *account = account_get_default();
2537 vcal_manager_update_answer(event, account->address,
2538 account->name,
2539 ICAL_PARTSTAT_ACCEPTED,
2540 ICAL_CUTYPE_INDIVIDUAL);
2541 debug_print("can't find our accounts in event, adding default\n");
2543 vcal_manager_save_event(event, TRUE);
2544 folder_item_scan(folder->inbox);
2545 retVal = vcal_get_event_as_ical_str(event);
2546 vcal_manager_free_event(event);
2549 return retVal;
2552 /* please call vcalendar_refresh_folder_contents() after one or more
2553 * calls to this function */
2554 gchar* vcal_update_event(const gchar *vevent)
2556 VCalEvent *event = vcal_get_event_from_ical(vevent, NULL);
2557 gboolean r = FALSE;
2558 if (event) {
2559 r = vcal_delete_event(event->uid);
2560 vcal_manager_free_event(event);
2561 if (r)
2562 return vcal_add_event(vevent);
2564 return NULL;