[balsa/gmime3: 1/49] Move to libical
- From: Peter Bloomfield <peterb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa/gmime3: 1/49] Move to libical
- Date: Sun, 19 Jan 2020 19:14:49 +0000 (UTC)
commit f7ee53d02959aefd818788b1a2eca10cf85a5958
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Sun Jan 19 14:12:06 2020 -0500
Move to libical
finally, I have a patch ready which uses libical for displaying
text/calendar parts in Balsa. The old code was /very/ limited and had
almost no capabilities for displaying complex requests.
Although the new implementation eliminates the code for scanning the
events, I added a few more fields from the events as well as a *lot*
of code for decoding recurrence rules which may be *very* complex
(compare RFC 5545, Sect. 3.3.1). Actually, I implemented most of this
stuff (which amounts to ~½ of libbalsa/rfc2445.c) as in Thunderbird's
Lightning extension, so I /hope/ it covers most common cases. I have a
bunch of test messages, created in Thunderbird, to demonstrate parsing,
just let me know if you would like to check them (or try yourself…).
* README: fix package requirements
* configure.ac, meson.build: require libical >= 3.0.0 (note:
not tested for Meson builds)
* libbalsa/rfc2445.[ch]: mostly re-written, using the libical
parser and data types
* src/balsa-mime-widget-vcalendar.c: add additional event items;
use the new rfc2445.h api
* src/balsa-print-object-text.c: fix two pango test layout memory
leaks; add additional event items; use the new rfc2445.h api
* src/print-gtk.c: fix part selection for printing text/calendar
ChangeLog | 13 +
README | 9 +-
configure.ac | 1 +
libbalsa/rfc2445.c | 1380 +++++++++++++++++++++----------------
libbalsa/rfc2445.h | 63 +-
meson.build | 4 +-
src/balsa-mime-widget-vcalendar.c | 71 +-
src/balsa-print-object-text.c | 63 +-
src/print-gtk.c | 20 +-
9 files changed, 958 insertions(+), 666 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index e69cbaca7..c3500bf8b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2020-01-19 Albrecht Dreß <albrecht dress arcor de>
+
+ * README: fix package requirements
+ * configure.ac, meson.build: require libical >= 3.0.0 (note:
+ not tested for Meson builds)
+ * libbalsa/rfc2445.[ch]: mostly re-written, using the libical
+ parser and data types
+ * src/balsa-mime-widget-vcalendar.c: add additional event items;
+ use the new rfc2445.h api
+ * src/balsa-print-object-text.c: fix two pango test layout memory
+ leaks; add additional event items; use the new rfc2445.h api
+ * src/print-gtk.c: fix part selection for printing text/calendar
+
2020-01-19 Peter Bloomfield <pbloomfield bellsouth net>
balsa-index: Simplify managing idle handlers
diff --git a/README b/README
index 1556c1313..8e6dd3ba2 100644
--- a/README
+++ b/README
@@ -3,7 +3,7 @@ Balsa E-Mail Client 2.5.x
See ChangeLog for the list of the recent changes and NEWS for highlights.
-Copyright (C) 1997-2019 Stuart Parmenter and others
+Copyright (C) 1997-2020 Stuart Parmenter and others
See 'COPYING' for licence information.
@@ -39,12 +39,15 @@ should run './configure --help' to get an idea of them. More
complete descriptions are here.
Basically, Balsa requires
-- glib-2.0 >= 2.40.0
-- gtk+-3.0 >= 3.10.0
+- glib-2.0 >= 2.48.0
+- gtk+-3.0 >= 3.18.0
- gmime-2.6
- gio-2.0
- gthread-2.0
+- gnutls >= 3.0
- gpgme >= 1.6.0
+- libical >= 3.0.0
+- fribidi
--disable-more-warnings
Balsa by default is very sensitive to compilation warnings
diff --git a/configure.ac b/configure.ac
index d9452b288..0ab69bb36 100644
--- a/configure.ac
+++ b/configure.ac
@@ -230,6 +230,7 @@ gio-2.0
gthread-2.0
gnutls >= 3.0
fribidi
+libical >= 3.0.0
])
PKG_CHECK_MODULES(BALSA_AB, [
diff --git a/libbalsa/rfc2445.c b/libbalsa/rfc2445.c
index 46dee8855..ee358b236 100644
--- a/libbalsa/rfc2445.c
+++ b/libbalsa/rfc2445.c
@@ -1,7 +1,7 @@
/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
/*
- * VCalendar (RFC 2445) stuff
- * Copyright (C) 2009-2019 Albrecht Dreß <albrecht dress arcor de>
+ * VCalendar (RFC 5545 and 5546) stuff
+ * Copyright (C) 2009-2020 Albrecht Dreß <albrecht dress arcor de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,7 +20,6 @@
#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
# include "config.h"
#endif /* HAVE_CONFIG_H */
-#include "rfc2445.h"
#include <stdlib.h>
#include <string.h>
@@ -29,24 +28,17 @@
#include <glib-object.h>
#include "libbalsa.h"
-#include "missing.h"
-
-
-/* participant roles as defined by RFC 2446 */
-typedef enum {
- VCAL_ROLE_UNKNOWN = 0,
- VCAL_ROLE_CHAIR,
- VCAL_ROLE_REQ_PART,
- VCAL_ROLE_OPT_PART,
- VCAL_ROLE_NON_PART
-} LibBalsaVCalRole;
+#include "rfc2445.h"
struct _LibBalsaVCal {
GObject parent;
/* method */
- LibBalsaVCalMethod method;
+ icalproperty_method method;
+
+ /* VCALENDAR object */
+ icalcomponent *vcalendar;
/* linked list of VEVENT entries */
GList *vevent;
@@ -60,15 +52,19 @@ struct _LibBalsaVEvent {
LibBalsaAddress *organizer;
GList *attendee;
- time_t stamp;
- time_t start;
- gboolean start_date_only;
- time_t end;
- gboolean end_date_only;
+ icaltimetype stamp;
+ icaltimetype start;
+ icaltimetype end;
+ icaltimetype recurrence_id;
+ struct icaldurationtype duration;
+ struct icalrecurrencetype rrule;
+ icalproperty_status status;
gchar *uid;
+ int sequence;
gchar *summary;
gchar *location;
gchar *description;
+ GList *categories;
};
G_DEFINE_TYPE(LibBalsaVEvent, libbalsa_vevent, G_TYPE_OBJECT)
@@ -76,46 +72,23 @@ G_DEFINE_TYPE(LibBalsaVEvent, libbalsa_vevent, G_TYPE_OBJECT)
/* LibBalsaAddress extra object data */
typedef struct {
- LibBalsaVCalRole role;
- LibBalsaVCalPartStat part_stat;
- gboolean rsvp;
+ icalparameter_role role;
+ icalparameter_partstat part_stat;
+ icalparameter_rsvp rsvp;
} LibBalsaVCalInfo;
#define RFC2445_INFO "RFC2445:Info"
static void libbalsa_vcal_finalize(GObject *self);
-
static void libbalsa_vevent_finalize(GObject *self);
-static LibBalsaAddress *cal_address_2445_to_lbaddress(const gchar * uri,
- gchar ** attributes,
- gboolean
- is_organizer);
-
/* conversion helpers */
-static time_t date_time_2445_to_time_t(const gchar *date_time, const gchar *modifier, gboolean *date_only);
-static gchar *time_t_to_date_time_2445(time_t ttime);
-static gchar *text_2445_unescape(const gchar * text);
-static gchar *text_2445_escape(const gchar * text);
-static const gchar *vcal_role_to_str(LibBalsaVCalRole role);
-static LibBalsaVCalMethod vcal_str_to_method(const gchar * method);
-static LibBalsaVCalRole vcal_str_to_role(const gchar * role);
-static LibBalsaVCalPartStat vcal_str_to_part_stat(const gchar * pstat);
-
-
-static struct {
- gchar *str_2445;
- gchar *hr_text;
-} pstats[] = {
- { NULL, N_("unknown") },
- { "NEEDS-ACTION", N_("needs action") },
- { "ACCEPTED", N_("accepted") },
- { "DECLINED", N_("declined") },
- { "TENTATIVE", N_("tentatively accepted") },
- { "DELEGATED", N_("delegated") },
- { "COMPLETED", N_("completed") },
- { "IN-PROCESS", N_("in process") }
-};
+static LibBalsaVCal *vcalendar_extract(const gchar *vcal_buf);
+static LibBalsaAddress *cal_address_5545_to_lbaddress(icalproperty *prop,
+
gboolean is_organizer);
+static const gchar *vcal_role_to_str(icalparameter_role role);
+static gchar *icaltime_str(icaltimetype ical_time,
+ const gchar *format_str);
/* --- VCal GObject stuff --- */
@@ -132,8 +105,7 @@ libbalsa_vcal_class_init(LibBalsaVCalClass *klass)
static void
libbalsa_vcal_init(LibBalsaVCal *self)
{
- self->method = ITIP_UNKNOWN;
- self->vevent = NULL;
+ self->method = ICAL_METHOD_NONE;
}
@@ -146,18 +118,14 @@ libbalsa_vcal_finalize(GObject *self)
if (vcal->vevent != NULL) {
g_list_free_full(vcal->vevent, g_object_unref);
}
+ if (vcal->vcalendar != NULL) {
+ icalcomponent_free(vcal->vcalendar);
+ }
(*parent_class->finalize)(self);
}
-LibBalsaVCal *
-libbalsa_vcal_new(void)
-{
- return LIBBALSA_VCAL(g_object_new(LIBBALSA_TYPE_VCAL, NULL));
-}
-
-
/* --- VEvent GObject stuff --- */
static void
libbalsa_vevent_class_init(LibBalsaVEventClass * klass)
@@ -171,7 +139,12 @@ libbalsa_vevent_class_init(LibBalsaVEventClass * klass)
static void
libbalsa_vevent_init(LibBalsaVEvent *self)
{
- self->start = self->end = self->stamp = (time_t) (-1);
+ self->start = icaltime_null_time();
+ self->end = icaltime_null_time();
+ self->stamp = icaltime_null_time();
+ self->duration = icaldurationtype_null_duration();
+ self->rrule.freq = ICAL_NO_RECURRENCE;
+ self->status = ICAL_STATUS_NONE;
}
@@ -191,193 +164,42 @@ libbalsa_vevent_finalize(GObject *self)
g_free(vevent->summary);
g_free(vevent->location);
g_free(vevent->description);
+ g_list_free_full(vevent->categories, g_free);
(*parent_class->finalize)(self);
}
-LibBalsaVEvent *
-libbalsa_vevent_new(void)
-{
- return LIBBALSA_VEVENT(g_object_new(LIBBALSA_TYPE_VEVENT, NULL));
-}
-
-
-#define STR_REPL_2445_TXT(s, v) \
- do { \
- g_free(s); \
- s = text_2445_unescape(v); \
- } while (0)
-
-/*
- * Find the first occurrence of c in s that is not in a double-quoted
- * string.
- *
- * Note that if c is '\0', the return value is NULL, *not* a pointer to
- * the terminating '\0' (c.f. strchr).
- */
-static gchar *
-vcal_strchr(gchar * s, gint c)
-{
- gboolean is_quoted = FALSE;
-
- while (*s) {
- if (*s == '"')
- is_quoted = !is_quoted;
- else if (*s == c && !is_quoted)
- return s;
- ++s;
- }
-
- return NULL;
-}
-
-/*
- * Split the string s at unquoted occurrences of c.
- *
- * Note that c is a single character, not a string, as in g_strsplit,
- * and that there is no limit on the number of splits.
- *
- * Returns a newly-allocated NULL-terminated array of strings. Use
- * g_strfreev() to free it.
- */
-static gchar **
-vcal_strsplit_at_char(gchar * s, gint c)
-{
- GPtrArray *array = g_ptr_array_new();
- gchar *p;
-
- while ((p = vcal_strchr(s, c))) {
- g_ptr_array_add(array, g_strndup(s, p - s));
- s = p + 1;
- }
- g_ptr_array_add(array, g_strdup(s));
- g_ptr_array_add(array, NULL);
-
- return (gchar **) g_ptr_array_free(array, FALSE);
-}
-
/* parse a text/calendar part and convert it into a LibBalsaVCal object */
LibBalsaVCal *
libbalsa_vcal_new_from_body(LibBalsaMessageBody * body)
{
- LibBalsaVCal *retval;
- gchar *charset;
- gchar *method;
+ LibBalsaVCal *retval = NULL;
gchar *vcal_buf;
- gchar *p;
- gchar **lines;
- LibBalsaVEvent *event;
- gboolean in_embedded;
- int k;
g_return_val_if_fail(body != NULL, NULL);
/* get the body buffer */
- if (libbalsa_message_body_get_content(body, &vcal_buf, NULL) <= 0)
- return NULL;
-
- /* check if the body has a charset (default is utf-8, see '2445,
- * sect 4.1.4) */
- charset = libbalsa_message_body_get_parameter(body, "charset");
- if (charset && g_ascii_strcasecmp(charset, "utf-8")) {
- gsize written;
- gchar *conv_buf;
-
- conv_buf =
- g_convert(vcal_buf, -1, "utf-8", charset, NULL, &written,
- NULL);
- g_free(vcal_buf);
- vcal_buf = conv_buf;
- }
- g_free(charset);
- if (!vcal_buf)
- return NULL;
-
- /* o.k., create a new object */
- retval = libbalsa_vcal_new();
-
- /* replace \r\n by \n */
- p = strchr(vcal_buf, '\r');
- while (p) {
- if (p[1] =='\n')
- memmove(p, p + 1, strlen(p + 1) + 1);
- p = strchr(p + 1, '\r');
- }
+ if (libbalsa_message_body_get_content(body, &vcal_buf, NULL) > 0) {
+ gchar *charset;
- /* unfold the body and split into lines */
- p = strchr(vcal_buf, '\n');
- while (p) {
- if (p[1] == '\t' || p[1] == ' ')
- memmove(p, p + 2, strlen(p + 2) + 1);
- p = strchr(p + 1, '\n');
- }
- lines = g_strsplit(vcal_buf, "\n", -1);
- g_free(vcal_buf);
-
- /* scan lines to extract vevent(s) */
- event = NULL;
- method = NULL;
- in_embedded = FALSE;
- for (k = 0; lines[k]; k++) {
- if (!event) {
- if (!method && !g_ascii_strncasecmp("METHOD:", lines[k], 7))
- method = g_strdup(lines[k] + 7);
- if (!g_ascii_strcasecmp("BEGIN:VEVENT", lines[k]))
- event = libbalsa_vevent_new();
- } else if (strlen(lines[k])) {
- gchar *value = vcal_strchr(lines[k], ':');
- gchar **entry;
-
- if (value)
- *value++ = '\0';
- entry = vcal_strsplit_at_char(lines[k], ';');
- if (!g_ascii_strcasecmp(entry[0], "END")) {
- if (value && !g_ascii_strcasecmp(value, "VEVENT")) {
- retval->vevent = g_list_append(retval->vevent, event);
- event = NULL;
- } else {
- in_embedded = FALSE;
- }
- } else if (!g_ascii_strcasecmp(entry[0], "BEGIN"))
- in_embedded = TRUE;
- else if (!in_embedded) {
- if (!g_ascii_strcasecmp(entry[0], "DTSTART"))
- event->start = date_time_2445_to_time_t(value, entry[1], &event->start_date_only);
- else if (!g_ascii_strcasecmp(entry[0], "DTEND"))
- event->end = date_time_2445_to_time_t(value, entry[1], &event->end_date_only);
- else if (!g_ascii_strcasecmp(entry[0], "DTSTAMP"))
- event->stamp = date_time_2445_to_time_t(value, entry[1], NULL);
- else if (!g_ascii_strcasecmp(entry[0], "UID"))
- STR_REPL_2445_TXT(event->uid, value);
- else if (!g_ascii_strcasecmp(entry[0], "SUMMARY"))
- STR_REPL_2445_TXT(event->summary, value);
- else if (!g_ascii_strcasecmp(entry[0], "LOCATION"))
- STR_REPL_2445_TXT(event->location, value);
- else if (!g_ascii_strcasecmp(entry[0], "DESCRIPTION"))
- STR_REPL_2445_TXT(event->description, value);
- else if (!g_ascii_strcasecmp(entry[0], "ORGANIZER")) {
- if (event->organizer)
- g_object_unref(event->organizer);
- event->organizer =
- cal_address_2445_to_lbaddress(value, entry + 1, TRUE);
- } else if (!g_ascii_strcasecmp(entry[0], "ATTENDEE"))
- event->attendee =
- g_list_prepend(event->attendee,
- cal_address_2445_to_lbaddress(value,
- entry + 1,
- FALSE));
- }
- g_strfreev(entry);
- }
- }
- g_strfreev(lines);
- if (event)
- g_object_unref(event);
+ /* check if the body has a charset (default is utf-8, see '2445, sect 4.1.4) */
+ charset = libbalsa_message_body_get_parameter(body, "charset");
+ if ((charset != NULL) && (g_ascii_strcasecmp(charset, "utf-8") != 0)) {
+ gchar *conv_buf;
+
+ conv_buf = g_convert(vcal_buf, -1, "utf-8", charset, NULL, NULL, NULL);
+ g_free(vcal_buf);
+ vcal_buf = conv_buf;
+ }
+ g_free(charset);
- /* set the method */
- retval->method = vcal_str_to_method(method);
- g_free(method);
+ if (vcal_buf != NULL) {
+ /* o.k., create a new object */
+ retval = vcalendar_extract(vcal_buf);
+ g_free(vcal_buf);
+ }
+ }
return retval;
}
@@ -386,7 +208,7 @@ libbalsa_vcal_new_from_body(LibBalsaMessageBody * body)
/* return a rfc 2445 attendee (i.e. a LibBalsaAddress w/ extra information)
* as a human-readable string */
gchar *
-libbalsa_vcal_attendee_to_str(LibBalsaAddress * person)
+libbalsa_vcal_attendee_to_str(LibBalsaAddress *person)
{
GString *retval;
gchar *str;
@@ -397,16 +219,17 @@ libbalsa_vcal_attendee_to_str(LibBalsaAddress * person)
retval = g_string_new("");
info = g_object_get_data(G_OBJECT(person), RFC2445_INFO);
- if (info->role != VCAL_ROLE_UNKNOWN)
- g_string_printf(retval, "%s ", vcal_role_to_str(info->role));
+ if (info->role != ICAL_ROLE_NONE) {
+ g_string_printf(retval, "%s ", vcal_role_to_str(info->role));
+ }
str = libbalsa_address_to_gchar(person, -1);
retval = g_string_append(retval, str);
g_free(str);
- if (info->part_stat != VCAL_PSTAT_UNKNOWN)
- g_string_append_printf(retval, " (%s)",
- libbalsa_vcal_part_stat_to_str(info->part_stat));
+ if (info->part_stat != ICAL_PARTSTAT_NONE) {
+ g_string_append_printf(retval, " (%s)", libbalsa_vcal_part_stat_to_str(info->part_stat));
+ }
return g_string_free(retval, FALSE);
}
@@ -420,10 +243,8 @@ libbalsa_vcal_attendee_rsvp(LibBalsaAddress * person)
LibBalsaVCalInfo *info;
g_return_val_if_fail(LIBBALSA_IS_ADDRESS(person), FALSE);
-
info = g_object_get_data(G_OBJECT(person), RFC2445_INFO);
-
- return info->rsvp;
+ return info->rsvp == ICAL_RSVP_TRUE;
}
@@ -459,50 +280,27 @@ libbalsa_vevent_description(LibBalsaVEvent *event)
}
-time_t
-libbalsa_vevent_timestamp(LibBalsaVEvent *event, LibBalsaVEventTimestamp which)
+gchar *
+libbalsa_vevent_time_str(LibBalsaVEvent *event, LibBalsaVEventTimestamp which, const gchar *format_str)
{
- time_t result;
-
- g_return_val_if_fail(LIBBALSA_IS_VEVENT(event) &&
- (which >= VEVENT_DATETIME_STAMP) && (VEVENT_DATETIME_END), (time_t) -1);
- switch (which) {
- case VEVENT_DATETIME_STAMP:
- result = event->stamp;
- break;
- case VEVENT_DATETIME_START:
- result = event->start;
- break;
- case VEVENT_DATETIME_END:
- result = event->end;
- break;
- default:
- g_assert_not_reached();
- }
- return result;
-}
+ gchar *result = NULL;
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), NULL);
-gboolean
-libbalsa_vevent_timestamp_date_only(LibBalsaVEvent *event, LibBalsaVEventTimestamp which)
-{
- gboolean result;
-
- g_return_val_if_fail(LIBBALSA_IS_VEVENT(event) &&
- (which >= VEVENT_DATETIME_STAMP) && (VEVENT_DATETIME_END), FALSE);
switch (which) {
case VEVENT_DATETIME_STAMP:
- result = FALSE;
+ result = icaltime_str(event->stamp, format_str);
break;
case VEVENT_DATETIME_START:
- result = event->start_date_only;
+ result = icaltime_str(event->start, format_str);
break;
case VEVENT_DATETIME_END:
- result = event->end_date_only;
+ result = icaltime_str(event->end, format_str);
break;
default:
g_assert_not_reached();
}
+
return result;
}
@@ -523,381 +321,807 @@ libbalsa_vevent_attendee(LibBalsaVEvent *event, guint nth_attendee)
}
-/* return a new buffer containing a proper reply to an event for a new
- * participant status */
+guint
+libbalsa_vevent_category_count(LibBalsaVEvent *event)
+{
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), 0U);
+ return g_list_length(event->categories);
+}
+
+
gchar *
-libbalsa_vevent_reply(const LibBalsaVEvent * event, const gchar * sender,
- LibBalsaVCalPartStat new_stat)
+libbalsa_vevent_category_str(LibBalsaVEvent *event)
{
- GString *retval;
- gchar *buf;
- gssize p;
- gchar *eol;
+ gchar *res = NULL;
- /* check for allowed new state and sender */
- g_return_val_if_fail((new_stat == VCAL_PSTAT_ACCEPTED ||
- new_stat == VCAL_PSTAT_DECLINED ||
- new_stat == VCAL_PSTAT_TENTATIVE) &&
- sender != NULL, NULL);
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), NULL);
+ if (event->categories != NULL) {
+ GString *buffer;
+ GList *p;
+
+ buffer = g_string_new((const gchar *) event->categories->data);
+ for (p = event->categories->next; p != NULL; p = p->next) {
+ g_string_append_c(buffer, '\n');
+ g_string_append(buffer, (const gchar *) p->data);
+ }
+ res = g_string_free(buffer, FALSE);
+ }
+ return res;
+}
- /* build the part */
- retval = g_string_new("BEGIN:VCALENDAR\nVERSION:2.0\n"
- "PRODID:-//GNOME//Balsa " BALSA_VERSION "//EN\n"
- "METHOD:REPLY\nBEGIN:VEVENT\n");
- buf = time_t_to_date_time_2445(time(NULL));
- g_string_append_printf(retval, "DTSTAMP:%s\n", buf);
- g_free(buf);
- g_string_append_printf(retval, "ATTENDEE;PARTSTAT=%s:mailto:%s\n",
- pstats[(int) new_stat].str_2445, sender);
- if (event->uid)
- g_string_append_printf(retval, "UID:%s\n", event->uid);
- if (event->organizer != NULL) {
- const gchar *addr = libbalsa_address_get_addr(event->organizer);
- if (addr != NULL)
- g_string_append_printf(retval, "ORGANIZER:mailto:%s\n", addr);
- }
- if (event->summary) {
- buf = text_2445_escape(event->summary);
- g_string_append_printf(retval, "SUMMARY:%s\n", buf);
- g_free(buf);
- }
- if (event->description) {
- buf = text_2445_escape(event->description);
- g_string_append_printf(retval, "DESCRIPTION:%s\n", buf);
- g_free(buf);
- }
- if (event->start != (time_t) - 1) {
- buf = time_t_to_date_time_2445(event->start);
- g_string_append_printf(retval, "DTSTART:%s\n", buf);
- g_free(buf);
- }
- if (event->end != (time_t) - 1) {
- buf = time_t_to_date_time_2445(event->end);
- g_string_append_printf(retval, "DTEND:%s\n", buf);
- g_free(buf);
- }
- retval = g_string_append(retval, "END:VEVENT\nEND:VCALENDAR");
-
- /* fold */
- p = 0;
- eol = strchr(retval->str, '\n');
- while (eol) {
- if (eol - (retval->str + p) > 74) {
- retval = g_string_insert(retval, p + 74, "\n ");
- p += 76;
- } else
- p = eol - retval->str + 1;
- eol = strchr(retval->str + p, '\n');
- }
+gchar *
+libbalsa_vevent_duration_str(LibBalsaVEvent *event)
+{
+ gchar *res = NULL;
- /* done */
- return g_string_free(retval, FALSE);
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), NULL);
+ if (icaldurationtype_is_null_duration(event->duration) == 0) {
+ GString *buffer = g_string_new(NULL);
+
+ if (event->duration.weeks > 0) {
+ g_string_append_printf(buffer, "%d %s", event->duration.weeks,
+ ngettext("week", "weeks", event->duration.weeks));
+ }
+ if (event->duration.days > 0) {
+ if (buffer->len > 0U) {
+ g_string_append(buffer, ", ");
+ }
+ g_string_append_printf(buffer, "%d %s", event->duration.days,
+ ngettext("day", "days", event->duration.days));
+ }
+ if (event->duration.hours > 0) {
+ if (buffer->len > 0U) {
+ g_string_append(buffer, ", ");
+ }
+ g_string_append_printf(buffer, "%d %s", event->duration.hours,
+ ngettext("hour", "hours", event->duration.hours));
+ }
+ if (event->duration.minutes > 0) {
+ if (buffer->len > 0U) {
+ g_string_append_c(buffer, ' ');
+ }
+ g_string_append_printf(buffer, "%d %s", event->duration.minutes,
+ ngettext("minute", "minutes", event->duration.minutes));
+ }
+ if (event->duration.seconds > 0) {
+ if (buffer->len > 0U) {
+ g_string_append_c(buffer, ' ');
+ }
+ g_string_append_printf(buffer, "%d %s", event->duration.seconds,
+ ngettext("second", "seconds", event->duration.seconds));
+ }
+ res = g_string_free(buffer, FALSE);
+ }
+ return res;
}
-/* -- rfc 2445 parser helper functions -- */
+icalproperty_status
+libbalsa_vevent_status(LibBalsaVEvent *event)
+{
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), ICAL_STATUS_NONE);
+ return event->status;
+}
-/* convert a rfc 2445 time string into a time_t value */
-// FIXME - what about entries containing a TZID?
-static time_t
-date_time_2445_to_time_t(const gchar *date_time, const gchar *modifier, gboolean *date_only)
-{
- gint len;
- time_t the_time = (time_t) (-1);
-
- g_return_val_if_fail(date_time != NULL, (time_t) (-1));
- len = strlen(date_time);
-
- /* must be yyyymmddThhmmssZ? */
- if (((len == 15) || ((len == 16) && (date_time[15] == 'Z'))) &&
- (date_time[8] == 'T')) {
-#if GLIB_CHECK_VERSION(2, 56, 0)
- GDateTime *datetime;
-
- datetime = g_date_time_new_from_iso8601(date_time, NULL);
- if (datetime != NULL) {
- the_time = g_date_time_to_unix(datetime);
- if (date_only != NULL) {
- *date_only = FALSE;
- }
- g_date_time_unref(datetime);
- }
-#else /* GLIB_CHECK_VERSION(2, 56, 0) */
- GTimeVal timeval;
-
- /* the rfc2445 date-time is a special case of an iso8901 date/time value... */
- if (g_time_val_from_iso8601(date_time, &timeval)) {
- the_time = timeval.tv_sec;
- if (date_only != NULL) {
- *date_only = FALSE;
- }
- }
-#endif /* GLIB_CHECK_VERSION(2, 56, 0) */
- } else if ((modifier!= NULL) && (g_ascii_strcasecmp(modifier, "VALUE=DATE") == 0) && (len == 8)) {
- struct tm tm;
-
- /* date only (yyyymmdd) */
- memset(&tm, 0, sizeof(tm));
- if (strptime(date_time, "%Y%m%d", &tm) != NULL) {
- the_time = mktime(&tm);
- if (date_only != NULL) {
- *date_only = TRUE;
- }
- }
+
+const gchar *
+libbalsa_vevent_status_str(LibBalsaVEvent *event)
+{
+ static struct {
+ icalproperty_status status;
+ gchar *status_str;
+ } status[] = {
+ { ICAL_STATUS_NONE, N_("unknown") },
+ { ICAL_STATUS_TENTATIVE, N_("event is tentative") },
+ { ICAL_STATUS_CONFIRMED, N_("event is definite") },
+ { ICAL_STATUS_CANCELLED, N_("event was cancelled") }
+ };
+ guint n;
+
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), NULL);
+ for (n = 0U; (n < G_N_ELEMENTS(status)) && (status[n].status != event->status); n++) {
+ /* nothing to do */
}
+ return (n < G_N_ELEMENTS(status)) ? status[n].status_str : status[0].status_str;
+}
- return the_time;
+icalproperty_method
+libbalsa_vcal_method(LibBalsaVCal *vcal)
+{
+ g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal), ICAL_METHOD_NONE);
+ return vcal->method;
}
-static gchar *
-time_t_to_date_time_2445(time_t ttime)
+const gchar *
+libbalsa_vcal_method_str(LibBalsaVCal *vcal)
{
- gchar *retval = NULL;
- GDateTime *date_time;
+ static struct {
+ icalproperty_method method;
+ gchar *meth_str;
+ } methods[] = {
+ { ICAL_METHOD_NONE, N_("unknown") },
+ { ICAL_METHOD_PUBLISH, N_("Event Notification") },
+ { ICAL_METHOD_REQUEST, N_("Event Request") },
+ { ICAL_METHOD_REPLY, N_("Reply to Event Request") },
+ { ICAL_METHOD_CANCEL, N_("Event Cancellation") }
+ };
+ guint n;
- date_time = g_date_time_new_from_unix_utc(ttime);
- if (date_time != NULL) {
- retval = g_date_time_format(date_time, "%Y%m%dT%H%M%SZ");
- g_date_time_unref(date_time);
- }
- return retval;
+ for (n = 0U; (n < G_N_ELEMENTS(methods)) && (methods[n].method != vcal->method); n++) {
+ /* nothing to do */
+ }
+ return (n < G_N_ELEMENTS(methods)) ? methods[n].meth_str : methods[0].meth_str;
}
-/* unescape a text from rfc 2445 format to a plain string */
-static gchar *
-text_2445_unescape(const gchar * text)
-{
- gchar *retval;
- gchar *p;
-
- g_return_val_if_fail(text != NULL, NULL);
- retval = g_strdup(text);
- p = strchr(retval, '\\');
- while (p) {
- if (p[1] == 'n' || p[1] == 'N')
- p[1] = '\n';
- memmove(p, p + 1, strlen(p + 1) + 1);
- p = strchr(retval, '\\');
+/* return a rfc 2445 participant status as human-readable string */
+const gchar *
+libbalsa_vcal_part_stat_to_str(icalparameter_partstat pstat)
+{
+ static struct {
+ icalparameter_partstat partstat;
+ gchar *partstat_str;
+ } partstats[] = {
+ { ICAL_PARTSTAT_NONE, N_("unknown") },
+ { ICAL_PARTSTAT_NEEDSACTION, N_("needs action") },
+ { ICAL_PARTSTAT_ACCEPTED, N_("accepted") },
+ { ICAL_PARTSTAT_DECLINED, N_("declined") },
+ { ICAL_PARTSTAT_TENTATIVE, N_("tentatively accepted") },
+ { ICAL_PARTSTAT_DELEGATED, N_("delegated") }
+ };
+ guint n;
+
+ for (n = 0U; (n < G_N_ELEMENTS(partstats)) && (partstats[n].partstat != pstat); n++) {
+ /* nothing to do */
}
- return retval;
+ return (n < G_N_ELEMENTS(partstats)) ? partstats[n].partstat_str : partstats[0].partstat_str;
}
-/* unescape a text from rfc 2445 format to a plain string */
-static gchar *
-text_2445_escape(const gchar * text)
+guint
+libbalsa_vcal_vevent_count(LibBalsaVCal *vcal)
{
- GString *retval;
- static const gchar *do_escape = "\\;,\n";
- const gchar *esc_p;
-
- g_return_val_if_fail(text != NULL, NULL);
- retval = g_string_new(text);
- for (esc_p = do_escape; *esc_p; esc_p++) {
- gchar *enc_p = strchr(retval->str, *esc_p);
+ g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal), 0U);
+ return g_list_length(vcal->vevent);
+}
- while (enc_p) {
- gssize inspos = enc_p - retval->str;
- if (*enc_p == '\n')
- *enc_p = 'n';
- retval = g_string_insert_c(retval, inspos, '\\');
- enc_p = strchr(retval->str + inspos + 2, *esc_p);
- }
- }
- return g_string_free(retval, FALSE);
+LibBalsaVEvent *
+libbalsa_vcal_vevent(LibBalsaVCal *vcal, guint nth_event)
+{
+ g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal) && (nth_event < g_list_length(vcal->vevent)), NULL);
+ return (LibBalsaVEvent *) g_list_nth_data(vcal->vevent, nth_event);
}
-/* extract a rfc 2445 mailto address and into a LibBalsaAddress and add the
- * extra information (rfc 2445 attributes) as data to the returned object. */
-static LibBalsaAddress *
-cal_address_2445_to_lbaddress(const gchar * uri, gchar ** attributes,
- gboolean is_organizer)
+/* -- recurrence strings --
+ * According to RFC 5545, Sect. 3.3.1, these rules can be extremely complex. The following functions
basically implement the same
+ * features als Thunderbird's "Lightning" extension, which should be fine for the majority of cases in the
wild. However, there
+ * /may/ (and will) be cases where these function just fail... */
+
+static inline gboolean
+ical_ar_empty(const short *array)
{
- LibBalsaAddress *retval;
- LibBalsaVCalInfo *info;
+ return array[0] == ICAL_RECURRENCE_ARRAY_MAX;
+}
- /* must be a mailto: uri */
- if (g_ascii_strncasecmp("mailto:", uri, 7))
- return NULL;
-
- retval = libbalsa_address_new();
- libbalsa_address_append_addr(retval, uri + 7);
-
- info = g_new0(LibBalsaVCalInfo, 1);
- g_object_set_data_full(G_OBJECT(retval), RFC2445_INFO, info, g_free);
-
- if (!is_organizer)
- info->role = VCAL_ROLE_REQ_PART;
-
- if (attributes) {
- int n;
- gchar *the_attr;
-
- /* scan attributes for extra information */
- for (n = 0; (the_attr = attributes[n]); n++) {
- if (!g_ascii_strncasecmp(the_attr, "CN=", 3))
- libbalsa_address_set_full_name(retval, the_attr + 3);
- else if (!g_ascii_strncasecmp(the_attr, "ROLE=", 5))
- info->role = vcal_str_to_role(the_attr + 5);
- else if (!g_ascii_strncasecmp(the_attr, "PARTSTAT=", 9))
- info->part_stat = vcal_str_to_part_stat(the_attr + 9);
- else if (!g_ascii_strncasecmp(the_attr, "RSVP=", 5))
- info->rsvp = g_ascii_strcasecmp(the_attr + 5, "TRUE") == 0;
- }
- }
- return retval;
+static inline const gchar *
+day_name(gint day)
+{
+ static const gchar * day_names[7] =
+ { N_("Sunday"), N_("Monday"), N_("Tuesday"), N_("Wednesday"), N_("Thursday"), N_("Friday"),
N_("Saturday") };
+
+ return ((day >= 0) && (day < 7)) ? day_names[day] : "???";
}
-/* -- conversion helpers string <--> enumeration -- */
+/** \brief Check for "BYDAY" items in any order
+ *
+ * \param rrule recurrence rule
+ * \param want_days bit mask of expected items in "BYDAY" (bit 1 = Sunday, bit 7 = Saturday)
+ * \return TRUE if the "BYDAY" item contains exactly all requested week days in any order
+ */
+static gboolean
+ical_check_bydays(const struct icalrecurrencetype *rrule, guint want_days)
+{
+ guint have_days = 0U;
+ guint n;
-/* convert the passed method string into the enumeration */
-static LibBalsaVCalMethod
-vcal_str_to_method(const gchar * method)
+ for (n = 0U; (n < ICAL_BY_DAY_SIZE) && (rrule->by_day[n] != ICAL_RECURRENCE_ARRAY_MAX); n++) {
+ have_days |= (1U << rrule->by_day[n]);
+ }
+ return want_days == have_days;
+}
+
+
+static GString *
+vevent_recurrence_daily(const struct icalrecurrencetype *rrule)
{
- static struct {
- gchar *meth_id;
- LibBalsaVCalMethod meth;
- } meth_list[] = {
- { "PUBLISH", ITIP_PUBLISH },
- { "REQUEST", ITIP_REQUEST },
- { "REPLY", ITIP_REPLY },
- { "CANCEL", ITIP_CANCEL },
- { NULL, ITIP_UNKNOWN}
- };
- gint n;
+ GString *result = g_string_new(NULL);
+
+ if (ical_check_bydays(rrule, 0x7cU)) {
+ g_string_append(result, _("every weekday"));
+ } else if (rrule->interval == 1) {
+ g_string_append(result, _("every day"));
+ } else {
+ g_string_append_printf(result, _("every %d days"), rrule->interval);
+ }
+ return result;
+}
- if (!method)
- return ITIP_UNKNOWN;
- for (n = 0;
- meth_list[n].meth_id
- && g_ascii_strcasecmp(method, meth_list[n].meth_id); n++);
- return meth_list[n].meth;
+
+static GString *
+vevent_recurrence_weekly(const struct icalrecurrencetype *rrule)
+{
+ GString *result = g_string_new(NULL);
+
+ if (!ical_ar_empty(rrule->by_day)) {
+ gint n;
+
+ if (rrule->interval == 1) {
+ g_string_append(result, _("every "));
+ } else {
+ g_string_append_printf(result, _("every %d weeks on "), rrule->interval);
+ }
+ g_string_append(result, day_name(rrule->by_day[0] - 1));
+ for (n = 1; (n < ICAL_BY_DAY_SIZE) && (rrule->by_day[n] != ICAL_RECURRENCE_ARRAY_MAX); n++) {
+ if ((n < (ICAL_BY_DAY_SIZE - 1)) && (rrule->by_day[n + 1] !=
ICAL_RECURRENCE_ARRAY_MAX)) {
+ g_string_append_printf(result, ", %s", day_name(rrule->by_day[n] - 1));
+ } else {
+ g_string_append_printf(result, _(" and %s"), day_name(rrule->by_day[n] - 1));
+ }
+ }
+ } else {
+ if (rrule->interval == 1) {
+ g_string_append(result, _("every week"));
+ } else {
+ g_string_append_printf(result, _("every %d weeks"), rrule->interval);
+ }
+ }
+ return result;
}
-LibBalsaVCalMethod
-libbalsa_vcal_method(LibBalsaVCal *vcal)
+static void
+day_ordinal_append(GString *buffer, int day, const gchar *last_append, const gchar *ordinal_append)
{
- g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal) &&
- (vcal->method >= ITIP_UNKNOWN) && (vcal->method <= ITIP_CANCEL), ITIP_UNKNOWN);
- return vcal->method;
+ if (day == -1) {
+ g_string_append(buffer, _("the last"));
+ if (last_append != NULL) {
+ g_string_append_printf(buffer, " %s", last_append);
+ }
+ } else {
+ switch (day % 10) {
+ case 1:
+ g_string_append_printf(buffer, _("%dst"), day);
+ break;
+ case 2:
+ g_string_append_printf(buffer, _("%dnd"), day);
+ break;
+ case 3:
+ g_string_append_printf(buffer, _("%drd"), day);
+ break;
+ default:
+ g_string_append_printf(buffer, _("%dth"), day);
+ }
+ if (ordinal_append != NULL) {
+ g_string_append_printf(buffer, " %s", ordinal_append);
+ }
+ }
}
-/* return a rfc 2445 method as human-readable string */
-const gchar *
-libbalsa_vcal_method_str(LibBalsaVCal *vcal)
+
+static GString *
+vevent_recurrence_monthly(const struct icalrecurrencetype *rrule, const icaltimetype *start)
{
- static gchar *methods[] = {
- N_("unknown"),
- N_("Event Notification"),
- N_("Event Request"),
- N_("Reply to Event Request"),
- N_("Event Cancellation"),
- };
+ GString *result = g_string_new(NULL);
+
+ if (!ical_ar_empty(rrule->by_day)) {
+ /* we have a "BYDAY" rule */
+ if (ical_check_bydays(rrule, 0xfeU)) {
+ if (rrule->interval == 1) {
+ g_string_append(result, _("every day of every month"));
+ } else {
+ g_string_append_printf(result, _("every day of the month every %d months"),
rrule->interval);
+ }
+ return result; /* eject here so we don't append the month interval again...
*/
+ } else {
+ guint n;
+ guint every_mask = 0U;
+ GList *days = NULL;
+ GList *p;
+
+ /* collect all days repeating every week */
+ for (n = 0U; (n < ICAL_BY_DAY_SIZE) && (rrule->by_day[n] !=
ICAL_RECURRENCE_ARRAY_MAX); n++) {
+ int day_pos;
+
+ day_pos = icalrecurrencetype_day_position(rrule->by_day[n]);
+ if ((day_pos < -1) || (day_pos > 5)) {
+ return g_string_assign(result, _("rule too complex"));
+ } else if (day_pos == 0) {
+ int day_of_week;
+
+ day_of_week = icalrecurrencetype_day_day_of_week(rrule->by_day[n]);
+ every_mask |= 1U << day_of_week;
+ days = g_list_append(days, g_strdup_printf(_("every %s"),
day_name(day_of_week - 1)));
+ } else {
+ /* handled below... */
+ }
+ }
+
+ /* collect specific ones, but avoid something like "Monday and the last Monday" */
+ for (n = 0U; (n < ICAL_BY_DAY_SIZE) && (rrule->by_day[n] !=
ICAL_RECURRENCE_ARRAY_MAX); n++) {
+ int day_of_week;
+
+ day_of_week = icalrecurrencetype_day_day_of_week(rrule->by_day[n]);
+ if ((every_mask & (1U << day_of_week)) == 0U) {
+ int day_pos;
+ GString *buffer = g_string_new(NULL);
+
+ day_pos = icalrecurrencetype_day_position(rrule->by_day[n]);
+ day_ordinal_append(buffer, day_pos, day_name(day_of_week - 1),
day_name(day_of_week - 1));
+ days = g_list_append(days, g_string_free(buffer, FALSE));
+ }
+ }
+
+ /* glue the string together */
+ g_string_append(result, (const gchar *) days->data);
+ for (p = days->next; p != NULL; p = p->next) {
+ if (p->next != NULL) {
+ g_string_append_printf(result, ", %s", (const gchar *) p->data);
+ } else {
+ g_string_append_printf(result, _(" and %s"), (const gchar *) p->data);
+ }
+ }
+ g_list_free_full(days, g_free);
+ }
+ } else if (!ical_ar_empty(rrule->by_month_day)) {
+ /* we have a "BYMONTHDAY" rule */
+ guint n;
+
+ for (n = 0; (n < ICAL_BY_MONTHDAY_SIZE) && (rrule->by_month_day[n] !=
ICAL_RECURRENCE_ARRAY_MAX); n++) {
+ if (rrule->by_month_day[n] < -1) {
+ return g_string_assign(result, _("rule too complex"));
+ } else {
+ if (n > 0) {
+ if (rrule->by_month_day[n + 1] != ICAL_RECURRENCE_ARRAY_MAX) {
+ g_string_append(result, ", ");
+ } else {
+ g_string_append(result, _(" and "));
+ }
+ }
+ day_ordinal_append(result, rrule->by_month_day[n], _("day"), NULL);
+ }
+ }
+ } else {
+ day_ordinal_append(result, start->day, _("day"), NULL);
+ }
- g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal) &&
- (vcal->method >= ITIP_UNKNOWN) && (vcal->method <= ITIP_CANCEL), NULL);
- return methods[(int) vcal->method];
+ if (rrule->interval == 1) {
+ g_string_append(result, _(" of every month"));
+ } else {
+ g_string_append_printf(result, _(" of every %d months"), rrule->interval);
+ }
+
+ return result;
}
-/* return a rfc 2445 role as human-readable string */
-static const gchar *
-vcal_role_to_str(LibBalsaVCalRole role)
-{
- static gchar *roles[] = {
- N_("unknown"),
- N_("chair"),
- N_("required participant"),
- N_("optional participant"),
- N_("non-participant, information only")
- };
+static GString *
+vevent_recurrence_yearly(const struct icalrecurrencetype *rrule, const icaltimetype *start)
+{
+ static const gchar *mon_name[12] =
+ { N_("January"), N_("February"), N_("March"), N_("April"), N_("May"), N_("June"),
+ N_("July"), N_("August"), N_("September"), N_("October"), N_("November"), N_("December") };
+ GString *result = g_string_new(NULL);
+
+ /* rules which are too complex for Lightning, so ignore them here, too... */
+ if ((rrule->by_month[1] != ICAL_RECURRENCE_ARRAY_MAX) || (rrule->by_month_day[1] !=
ICAL_RECURRENCE_ARRAY_MAX) ||
+ (rrule->by_month_day[0] < -1)) {
+ return g_string_assign(result, _("rule too complex"));
+ }
- g_return_val_if_fail(role >= VCAL_ROLE_UNKNOWN
- && role <= VCAL_ROLE_NON_PART, NULL);
- return roles[(int) role];
+ if (ical_ar_empty(rrule->by_day)) {
+ /* RRULE:FREQ=YEARLY;BYMONTH=x;BYMONTHDAY=y.
+ * RRULE:FREQ=YEARLY;BYMONTHDAY=x (takes the month from the start date)
+ * RRULE:FREQ=YEARLY;BYMONTH=x (takes the day from the start date)
+ * RRULE:FREQ=YEARLY (takes month and day from the start date) */
+ const gchar *month;
+ GString *day = g_string_new(NULL);
+
+ if (ical_ar_empty(rrule->by_month)) {
+ month = mon_name[start->month - 1];
+ } else {
+ month = mon_name[rrule->by_month[0] - 1];
+ }
+ if (ical_ar_empty(rrule->by_month_day)) {
+ day_ordinal_append(day, start->day, NULL, NULL);
+ } else {
+ day_ordinal_append(day, rrule->by_month_day[0], _("day"), NULL);
+ }
+ if (rrule->interval == 1) {
+ g_string_append_printf(result, _("every %s %s"), month, day->str);
+ } else {
+ g_string_append_printf(result, _("every %d years on %s %s"), rrule->interval, month,
day->str);
+ }
+ g_string_free(day, TRUE);
+ } else if (!ical_ar_empty(rrule->by_month) && !ical_ar_empty(rrule->by_day)) {
+ /* RRULE:FREQ=YEARLY;BYMONTH=x;BYDAY=y1,y2,... */
+ const gchar *month = mon_name[rrule->by_month[0] - 1];
+
+ if (ical_check_bydays(rrule, 0x7cU)) {
+ /* every day of the month */
+ if (rrule->interval == 1) {
+ g_string_append_printf(result, _("every day of %s"), month);
+ } else {
+ g_string_append_printf(result, _("every %d years every day of %s"),
rrule->interval, month);
+ }
+ } else if (rrule->by_day[1] == ICAL_RECURRENCE_ARRAY_MAX) {
+ int day_pos;
+ int day_of_week;
+
+ day_pos = icalrecurrencetype_day_position(rrule->by_day[0]);
+ day_of_week = icalrecurrencetype_day_day_of_week(rrule->by_day[0]);
+ if (day_pos == 0) {
+ if (rrule->interval == 1) {
+ g_string_append_printf(result, _("every %s of %s"),
day_name(day_of_week - 1), month);
+ } else {
+ g_string_append_printf(result, _("every %d years on every %s of %s"),
rrule->interval,
+ day_name(day_of_week - 1), month);
+ }
+ } else if ((day_pos >= -1) && (day_pos <= 5)) {
+ GString *day = g_string_new(NULL);
+
+ day_ordinal_append(day, day_pos, day_name(day_of_week - 1),
day_name(day_of_week - 1));
+ if (rrule->interval == 1) {
+ g_string_append_printf(result, _("the %s of every %s"), day->str,
month);
+ } else {
+ g_string_append_printf(result, _("every %d years on the %s of %s"),
rrule->interval, day->str, month);
+ }
+ g_string_free(day, TRUE);
+ } else {
+ g_string_assign(result, _("rule too complex"));
+ }
+ } else {
+ /* currently we don't support yearly rules with more than one BYDAY element or
exactly 7 elements with all the weekdays
+ * (the "every day" case) */
+ g_string_assign(result, _("rule too complex"));
+ }
+ } else {
+ return g_string_assign(result, _("rule too complex"));
+ }
+
+ return result;
}
-/* convert the passed role string into the enumeration */
-static LibBalsaVCalRole
-vcal_str_to_role(const gchar * role)
+gchar *
+libbalsa_vevent_recurrence_str(LibBalsaVEvent *event, const gchar *format_str)
{
- static struct {
- gchar *role_id;
- LibBalsaVCalRole role;
- } role_list[] = {
- { "CHAIR", VCAL_ROLE_CHAIR },
- { "REQ-PARTICIPANT", VCAL_ROLE_REQ_PART },
- { "OPT-PARTICIPANT", VCAL_ROLE_OPT_PART },
- { "NON-PARTICIPANT", VCAL_ROLE_NON_PART },
- { NULL, VCAL_ROLE_UNKNOWN}
- };
- gint n;
+ gchar *result = NULL;
+
+ g_return_val_if_fail(LIBBALSA_IS_VEVENT(event), NULL);
- if (!role)
- return VCAL_ROLE_UNKNOWN;
- for (n = 0;
- role_list[n].role_id
- && g_ascii_strcasecmp(role, role_list[n].role_id); n++);
- return role_list[n].role;
+ if ((event->rrule.freq >= ICAL_SECONDLY_RECURRENCE) && (event->rrule.freq <= ICAL_YEARLY_RECURRENCE))
{
+ GString *buffer;
+
+ switch (event->rrule.freq) {
+ case ICAL_SECONDLY_RECURRENCE:
+ buffer = g_string_new(_("secondly"));
+ break;
+ case ICAL_MINUTELY_RECURRENCE:
+ buffer = g_string_new(_("minutely"));
+ break;
+ case ICAL_HOURLY_RECURRENCE:
+ buffer = g_string_new(_("hourly"));
+ break;
+ case ICAL_DAILY_RECURRENCE:
+ buffer = vevent_recurrence_daily(&event->rrule);
+ break;
+ case ICAL_WEEKLY_RECURRENCE:
+ buffer = vevent_recurrence_weekly(&event->rrule);
+ break;
+ case ICAL_MONTHLY_RECURRENCE:
+ buffer = vevent_recurrence_monthly(&event->rrule, &event->start);
+ break;
+ case ICAL_YEARLY_RECURRENCE:
+ buffer = vevent_recurrence_yearly(&event->rrule, &event->start);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ /* end timestamp or count */
+ if (event->rrule.count > 0) {
+ g_string_append_printf(buffer, ", %d ", event->rrule.count);
+ g_string_append(buffer, ngettext(_("occurrence"), _("occurrences"),
event->rrule.count));
+ } else if (icaltime_is_null_time(event->rrule.until) == 0) {
+ gchar *timestr;
+
+ timestr = icaltime_str(event->rrule.until, format_str);
+ g_string_append_printf(buffer, _(" until %s"), timestr);
+ g_free(timestr);
+ } else {
+ /* nothing to do */
+ }
+
+ result = g_string_free(buffer, FALSE);
+ }
+ return result;
}
-/* return a rfc 2445 participant status as human-readable string */
-const gchar *
-libbalsa_vcal_part_stat_to_str(LibBalsaVCalPartStat pstat)
+/* return a new buffer containing a proper reply to an event for a new
+ * participant status
+ *
+ * According to RFC 5546, Sect. 3.2.3., the REPLY shall include the following components and properties:
+ * METHOD: MUST be REPLY
+ * VEVENT: ATTENDEE, DTSTAMP, ORGANIZER, RECURRENCE-ID (if present in the request), UID, SEQUENCE (if
present in the request)
+ */
+gchar *
+libbalsa_vevent_reply(const LibBalsaVEvent *event,
+ const gchar *sender,
+ icalparameter_partstat new_stat)
{
- g_return_val_if_fail(pstat >= VCAL_PSTAT_UNKNOWN
- && pstat <= VCAL_PSTAT_IN_PROCESS, NULL);
- return pstats[(int) pstat].hr_text;
+ icalcomponent *reply;
+ icalcomponent *ev_data;
+ icalproperty *prop;
+ gchar *buf;
+ gchar *reply_str;
+
+ /* check for allowed new state and sender */
+ g_return_val_if_fail((new_stat == ICAL_PARTSTAT_ACCEPTED ||
+ new_stat == ICAL_PARTSTAT_DECLINED ||
+ new_stat == ICAL_PARTSTAT_TENTATIVE) &&
+ sender != NULL, NULL);
+
+ reply = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
+
+ icalcomponent_add_property(reply, icalproperty_new_version("2.0"));
+ icalcomponent_add_property(reply, icalproperty_new_prodid("-//GNOME//Balsa " BALSA_VERSION "//EN"));
+ icalcomponent_add_property(reply, icalproperty_new_method(ICAL_METHOD_REPLY));
+ /* create the VEVENT */
+ ev_data = icalcomponent_new(ICAL_VEVENT_COMPONENT);
+ icalcomponent_add_component(reply, ev_data);
+
+ /* add ATTENDEE */
+ buf = g_strconcat("mailto:", sender, NULL);
+ prop = icalproperty_new_attendee(buf);
+ g_free(buf);
+ icalproperty_add_parameter(prop, icalparameter_new_partstat(new_stat));
+ icalcomponent_add_property(ev_data, prop);
+
+ /* add DTSTAMP */
+ icalcomponent_add_property(ev_data, icalproperty_new_dtstamp(icaltime_current_time_with_zone(NULL)));
+
+ /* add ORGANIZER - /should/ be present */
+ if (event->organizer != NULL) {
+ const gchar *addr = libbalsa_address_get_addr(event->organizer);
+
+ if (addr != NULL) {
+ icalcomponent_add_property(ev_data, icalproperty_new_organizer(addr));
+ }
+ }
+
+ /* add RECURRENCE-ID (if present) */
+ if (icaltime_is_null_time(event->recurrence_id) == 0) {
+ icalcomponent_add_property(ev_data, icalproperty_new_recurrenceid(event->recurrence_id));
+ }
+
+ /* add UID - /should/ be present */
+ if (event->uid != NULL) {
+ icalcomponent_add_property(ev_data, icalproperty_new_uid(event->uid));
+ }
+
+ /* add SEQUENCE (if present) */
+ if (event->sequence > 0) {
+ icalcomponent_add_property(ev_data, icalproperty_new_sequence(event->sequence));
+ }
+
+ reply_str = icalcomponent_as_ical_string(reply);
+ icalcomponent_free(reply);
+ return reply_str;
}
-guint
-libbalsa_vcal_vevents(LibBalsaVCal *vcal)
+
+/* -- rfc 2445 parser helper functions -- */
+
+static LibBalsaVCal *
+vcalendar_extract(const gchar *vcal_buf)
{
- g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal), 0U);
- return g_list_length(vcal->vevent);
+ LibBalsaVCal *retval = NULL;
+ icalcomponent *component;
+
+ component = icalparser_parse_string(vcal_buf);
+ if (component == NULL) {
+ return NULL;
+ }
+
+ /* verify - icalrestriction_check() seems to produce False Positives for some Thunderbird events, so
do *not* use it here */
+ if (icalcomponent_isa(component) == ICAL_VCALENDAR_COMPONENT) {
+ icalcomponent *item;
+
+ retval = LIBBALSA_VCAL(g_object_new(LIBBALSA_TYPE_VCAL, NULL));
+ retval->method = icalcomponent_get_method(component);
+ retval->vcalendar = component;
+
+ /* loop over all VEVENT items (if any) */
+ for (item = icalcomponent_get_first_component(component, ICAL_VEVENT_COMPONENT);
+ item != NULL;
+ item = icalcomponent_get_next_component(component, ICAL_VEVENT_COMPONENT)) {
+ LibBalsaVEvent *event;
+ icalproperty *prop;
+
+ event = LIBBALSA_VEVENT(g_object_new(LIBBALSA_TYPE_VEVENT, NULL));
+ retval->vevent = g_list_append(retval->vevent, event);
+
+ /* extract properties - DTSTAMP, DTSTART, DTEND
+ * note: there is no need to get the DURATION, as libical calculates the proper DTEND
if necessary. However, in
+ * particular for all-day events, the duration is sometimes easier to understand than
the (exclusive) end. */
+ event->stamp = icalcomponent_get_dtstamp(item);
+ event->start = icalcomponent_get_dtstart(item);
+ event->end = icalcomponent_get_dtend(item);
+ event->duration = icalcomponent_get_duration(item);
+ if (icaldurationtype_is_bad_duration(event->duration)) {
+ event->duration = icaldurationtype_null_duration();
+ }
+
+ /* RECURRENCE-ID */
+ event->recurrence_id = icalcomponent_get_recurrenceid(item);
+
+ /* STATUS */
+ event->status = icalcomponent_get_status(item);
+ if (event->status == (icalproperty_status) 0) {
+ event->status = ICAL_STATUS_NONE;
+ }
+
+ /* RRULE (only one is allowed, see RFC 5546, Sect. 3.2.2) */
+ prop = icalcomponent_get_first_property(item, ICAL_RRULE_PROPERTY);
+ if (prop != NULL) {
+ event->rrule = icalproperty_get_rrule(prop);
+ }
+
+ /* UID, SUMMARY, LOCATION, DESCRIPTION */
+ event->uid = g_strdup(icalcomponent_get_uid(item));
+ event->summary = g_strdup(icalcomponent_get_summary(item));
+ event->location = g_strdup(icalcomponent_get_location(item));
+ event->description = g_strdup(icalcomponent_get_description(item));
+
+ /* ORGANIZER */
+ prop = icalcomponent_get_first_property(item, ICAL_ORGANIZER_PROPERTY);
+ if (prop != NULL) {
+ event->organizer = cal_address_5545_to_lbaddress(prop, TRUE);
+ }
+
+ /* ATTENDEE */
+ for (prop = icalcomponent_get_first_property(item, ICAL_ATTENDEE_PROPERTY);
+ prop != NULL;
+ prop = icalcomponent_get_next_property(item, ICAL_ATTENDEE_PROPERTY)) {
+ LibBalsaAddress *attendee;
+
+ attendee = cal_address_5545_to_lbaddress(prop, FALSE);
+ if (attendee != NULL) {
+ event->attendee = g_list_prepend(event->attendee, attendee);
+ }
+ }
+
+ /* SEQUENCE (if present) */
+ event->sequence = icalcomponent_get_sequence(item);
+
+ /* CATEGORIES */
+ for (prop = icalcomponent_get_first_property(item, ICAL_CATEGORIES_PROPERTY);
+ prop != NULL;
+ prop = icalcomponent_get_next_property(item, ICAL_CATEGORIES_PROPERTY)) {
+ event->categories = g_list_append(event->categories,
g_strdup(icalproperty_get_categories(prop)));
+ }
+ }
+ }
+
+ return retval;
}
-LibBalsaVEvent *
-libbalsa_vcal_vevent(LibBalsaVCal *vcal, guint nth_event)
+
+/* extract a rfc 5545 mailto address and into a LibBalsaAddress and add the
+ * rfc 5545 attributes as data to the returned object. */
+static LibBalsaAddress *
+cal_address_5545_to_lbaddress(icalproperty *prop, gboolean is_organizer)
{
- g_return_val_if_fail(LIBBALSA_IS_VCAL(vcal) && (nth_event < g_list_length(vcal->vevent)), NULL);
- return (LibBalsaVEvent *) g_list_nth_data(vcal->vevent, nth_event);
+ const gchar *uri;
+ LibBalsaAddress *retval = NULL;
+
+ if (is_organizer) {
+ uri = icalproperty_get_organizer(prop);
+ } else {
+ uri = icalproperty_get_attendee(prop);
+ }
+
+ /* must be a mailto: uri */
+ if (g_ascii_strncasecmp("mailto:", uri, 7) == 0) {
+ LibBalsaVCalInfo *info;
+ icalparameter *param;
+
+ retval = libbalsa_address_new();
+ libbalsa_address_append_addr(retval, &uri[7]);
+
+ info = g_new0(LibBalsaVCalInfo, 1);
+ g_object_set_data_full(G_OBJECT(retval), RFC2445_INFO, info, g_free);
+ if (!is_organizer) {
+ info->role = ICAL_ROLE_REQPARTICIPANT;
+ } else {
+ info->role = ICAL_ROLE_NONE;
+ }
+ info->part_stat = ICAL_PARTSTAT_NONE;
+ info->rsvp = ICAL_RSVP_NONE;
+
+ for (param = icalproperty_get_first_parameter(prop, ICAL_ANY_PARAMETER);
+ param != NULL;
+ param = icalproperty_get_next_parameter(prop, ICAL_ANY_PARAMETER)) {
+ switch (icalparameter_isa(param)) {
+ case ICAL_CN_PARAMETER:
+ libbalsa_address_set_full_name(retval, icalparameter_get_cn(param));
+ break;
+ case ICAL_PARTSTAT_PARAMETER:
+ info->part_stat = icalparameter_get_partstat(param);
+ break;
+ case ICAL_ROLE_PARAMETER:
+ info->role = icalparameter_get_role(param);
+ break;
+ case ICAL_RSVP_PARAMETER:
+ info->rsvp = icalparameter_get_rsvp(param);
+ break;
+ default:
+ /* nothing to do, make gcc happy */
+ break;
+ }
+ }
+ }
+
+ return retval;
}
-/* convert the passed participant status string into the enumeration */
-static LibBalsaVCalPartStat
-vcal_str_to_part_stat(const gchar * pstat)
+
+/* return a rfc 2445 role as human-readable string */
+static const gchar *
+vcal_role_to_str(icalparameter_role role)
{
static struct {
- gchar *pstat_id;
- LibBalsaVCalPartStat pstat;
- } pstat_list[] = {
- { "NEEDS-ACTION", VCAL_PSTAT_NEEDS_ACTION },
- { "ACCEPTED", VCAL_PSTAT_ACCEPTED},
- { "DECLINED", VCAL_PSTAT_DECLINED },
- { "TENTATIVE", VCAL_PSTAT_TENTATIVE },
- { "DELEGATED", VCAL_PSTAT_DELEGATED },
- { "COMPLETED", VCAL_PSTAT_COMPLETED },
- { "IN-PROCESS", VCAL_PSTAT_IN_PROCESS },
- { NULL, VCAL_PSTAT_UNKNOWN }
+ icalparameter_role role;
+ gchar *role_str;
+ } roles[] = {
+ { ICAL_ROLE_NONE, N_("unknown") },
+ { ICAL_ROLE_CHAIR, N_("chair") },
+ { ICAL_ROLE_REQPARTICIPANT, N_("required participant") },
+ { ICAL_ROLE_OPTPARTICIPANT, N_("optional participant") },
+ { ICAL_ROLE_NONPARTICIPANT, N_("non-participant, information only") }
};
- gint n;
-
- if (!pstat)
- return VCAL_PSTAT_UNKNOWN;
- for (n = 0;
- pstat_list[n].pstat_id
- && g_ascii_strcasecmp(pstat, pstat_list[n].pstat_id); n++);
- return pstat_list[n].pstat;
+ guint n;
+
+ for (n = 0U; (n < G_N_ELEMENTS(roles)) && (roles[n].role != role); n++) {
+ /* nothing to do */
+ }
+ return (n < G_N_ELEMENTS(roles)) ? roles[n].role_str : roles[0].role_str;
+}
+
+
+static gchar *
+icaltime_str(icaltimetype ical_time, const gchar *format_str)
+{
+ gchar *result = NULL;
+
+ if (icaltime_is_null_time(ical_time) == 0) {
+ time_t unix_time;
+
+ unix_time = icaltime_as_timet_with_zone(ical_time, ical_time.zone);
+ if (ical_time.is_date == 0) {
+ result = libbalsa_date_to_utf8(unix_time, format_str);
+ } else {
+ result = libbalsa_date_to_utf8(unix_time, "%x");
+ }
+ }
+
+ return result;
}
diff --git a/libbalsa/rfc2445.h b/libbalsa/rfc2445.h
index d9676e44a..97b7022ca 100644
--- a/libbalsa/rfc2445.h
+++ b/libbalsa/rfc2445.h
@@ -1,7 +1,7 @@
/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
/*
- * VCalendar (RFC 2445) stuff
- * Copyright (C) 2009-2019 Albrecht Dreß <albrecht dress arcor de>
+ * VCalendar (RFC 5545 and 5546) stuff
+ * Copyright (C) 2009-2020 Albrecht Dreß <albrecht dress arcor de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
#include <glib.h>
#include <glib-object.h>
+#include <libical/ical.h>
#include <time.h>
#include "body.h"
#include "address.h"
@@ -40,28 +41,6 @@ G_DECLARE_FINAL_TYPE(LibBalsaVCal, libbalsa_vcal, LIBBALSA, VCAL, GObject)
G_DECLARE_FINAL_TYPE(LibBalsaVEvent, libbalsa_vevent, LIBBALSA, VEVENT, GObject)
-/* methods as defined by RFC 2446 */
-typedef enum {
- ITIP_UNKNOWN = 0,
- ITIP_PUBLISH,
- ITIP_REQUEST,
- ITIP_REPLY,
- ITIP_CANCEL
-} LibBalsaVCalMethod;
-
-/* participation status as defined by RFC 2445
- * (note: includes constants for VTODO) */
-typedef enum {
- VCAL_PSTAT_UNKNOWN = 0,
- VCAL_PSTAT_NEEDS_ACTION,
- VCAL_PSTAT_ACCEPTED,
- VCAL_PSTAT_DECLINED,
- VCAL_PSTAT_TENTATIVE,
- VCAL_PSTAT_DELEGATED,
- VCAL_PSTAT_COMPLETED,
- VCAL_PSTAT_IN_PROCESS
-} LibBalsaVCalPartStat;
-
typedef enum {
VEVENT_DATETIME_STAMP,
VEVENT_DATETIME_START,
@@ -69,30 +48,38 @@ typedef enum {
} LibBalsaVEventTimestamp;
-LibBalsaVCal *libbalsa_vcal_new(void);
LibBalsaVCal *libbalsa_vcal_new_from_body(LibBalsaMessageBody * body);
-LibBalsaVEvent *libbalsa_vevent_new(void);
-gchar *libbalsa_vevent_reply(const LibBalsaVEvent * event,
- const gchar * sender,
- LibBalsaVCalPartStat new_stat);
+gchar *libbalsa_vevent_reply(const LibBalsaVEvent *event,
+ const gchar *sender,
+ icalparameter_partstat new_stat);
-gchar *libbalsa_vcal_attendee_to_str(LibBalsaAddress * person);
-gboolean libbalsa_vcal_attendee_rsvp(LibBalsaAddress * person);
-LibBalsaVCalMethod libbalsa_vcal_method(LibBalsaVCal *vcal);
+gchar *libbalsa_vcal_attendee_to_str(LibBalsaAddress *person);
+gboolean libbalsa_vcal_attendee_rsvp(LibBalsaAddress *person);
+icalproperty_method libbalsa_vcal_method(LibBalsaVCal *vcal);
const gchar *libbalsa_vcal_method_str(LibBalsaVCal *vcal);
-const gchar *libbalsa_vcal_part_stat_to_str(LibBalsaVCalPartStat pstat);
-guint libbalsa_vcal_vevents(LibBalsaVCal *vcal);
-LibBalsaVEvent *libbalsa_vcal_vevent(LibBalsaVCal *vcal, guint nth_event);
+const gchar *libbalsa_vcal_part_stat_to_str(icalparameter_partstat pstat);
+guint libbalsa_vcal_vevent_count(LibBalsaVCal *vcal);
+LibBalsaVEvent *libbalsa_vcal_vevent(LibBalsaVCal *vcal,
+ guint nth_event);
LibBalsaAddress *libbalsa_vevent_organizer(LibBalsaVEvent *event);
const gchar *libbalsa_vevent_summary(LibBalsaVEvent *event);
const gchar *libbalsa_vevent_location(LibBalsaVEvent *event);
const gchar *libbalsa_vevent_description(LibBalsaVEvent *event);
-time_t libbalsa_vevent_timestamp(LibBalsaVEvent *event, LibBalsaVEventTimestamp which);
-gboolean libbalsa_vevent_timestamp_date_only(LibBalsaVEvent *event, LibBalsaVEventTimestamp which);
+icalproperty_status libbalsa_vevent_status(LibBalsaVEvent *event);
+gchar *libbalsa_vevent_time_str(LibBalsaVEvent *event,
+ LibBalsaVEventTimestamp which,
+ const gchar *format_str);
guint libbalsa_vevent_attendees(LibBalsaVEvent *event);
-LibBalsaAddress *libbalsa_vevent_attendee(LibBalsaVEvent *event, guint nth_attendee);
+LibBalsaAddress *libbalsa_vevent_attendee(LibBalsaVEvent *event,
+ guint
nth_attendee);
+guint libbalsa_vevent_category_count(LibBalsaVEvent *event);
+gchar *libbalsa_vevent_category_str(LibBalsaVEvent *event);
+gchar *libbalsa_vevent_duration_str(LibBalsaVEvent *event);
+const gchar *libbalsa_vevent_status_str(LibBalsaVEvent *event);
+gchar *libbalsa_vevent_recurrence_str(LibBalsaVEvent *event,
+ const gchar *format_str);
G_END_DECLS
diff --git a/meson.build b/meson.build
index bd571a6bd..64e1ca627 100644
--- a/meson.build
+++ b/meson.build
@@ -136,6 +136,7 @@ gthread_dep = dependency('gthread-2.0')
gnutls_dep = dependency('gnutls', version : '>= 3.0')
gpgme_dep = dependency('gpgme', version : '>= 1.6.0')
fribidi_dep = dependency('fribidi')
+libical_dep = dependency('libical', version : '>= 3.0.0')
# Dependencies for balsa
balsa_deps = [glib_dep,
@@ -145,7 +146,8 @@ balsa_deps = [glib_dep,
gthread_dep,
gnutls_dep,
gpgme_dep,
- fribidi_dep]
+ fribidi_dep,
+ libical_dep]
# Dependencies for balsa_ab:
balsa_ab_deps = [glib_dep,
diff --git a/src/balsa-mime-widget-vcalendar.c b/src/balsa-mime-widget-vcalendar.c
index d2e3ff347..c9fbf0d62 100644
--- a/src/balsa-mime-widget-vcalendar.c
+++ b/src/balsa-mime-widget-vcalendar.c
@@ -46,10 +46,11 @@ balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
BalsaMimeWidget *mw;
GtkWidget *label;
gchar *text;
+ gchar *markup_buf;
guint event_no;
LibBalsaMessage *lbm = balsa_message_get_message(bm);
gboolean may_reply = FALSE;
- InternetAddress *sender = NULL;;
+ InternetAddress *sender = NULL;
g_return_val_if_fail(mime_body != NULL, NULL);
g_return_val_if_fail(content_type != NULL, NULL);
@@ -63,14 +64,17 @@ balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
text = g_strdup_printf(_("This is an iTIP calendar “%s” message."),
libbalsa_vcal_method_str(vcal_obj));
- label = gtk_label_new(text);
+ markup_buf = g_markup_printf_escaped("<b>%s</b>", text);
g_free(text);
+ label = gtk_label_new(NULL);
+ gtk_label_set_markup(GTK_LABEL (label), markup_buf);
+ g_free(markup_buf);
gtk_widget_set_halign(label, GTK_ALIGN_START);
gtk_widget_set_valign(label, GTK_ALIGN_START);
gtk_container_add(GTK_CONTAINER(mw), label);
/* a reply may be created only for unread requests */
- if ((libbalsa_vcal_method(vcal_obj) == ITIP_REQUEST) &&
+ if ((libbalsa_vcal_method(vcal_obj) == ICAL_METHOD_REQUEST) &&
LIBBALSA_MESSAGE_IS_UNREAD(lbm)) {
LibBalsaMessageHeaders *headers;
@@ -93,11 +97,15 @@ balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
}
/* add events */
- for (event_no = 0U; event_no < libbalsa_vcal_vevents(vcal_obj); event_no++) {
+ for (event_no = 0U; event_no < libbalsa_vcal_vevent_count(vcal_obj); event_no++) {
+ GtkWidget *frame;
GtkWidget *event;
+ frame = gtk_frame_new(NULL);
+ gtk_container_add(GTK_CONTAINER(mw), frame);
event = balsa_vevent_widget(libbalsa_vcal_vevent(vcal_obj, event_no), may_reply, sender);
- gtk_container_add(GTK_CONTAINER(mw), event);
+ gtk_container_set_border_width(GTK_CONTAINER(event), 6U);
+ gtk_container_add(GTK_CONTAINER(frame), event);
}
g_object_unref(vcal_obj);
@@ -123,17 +131,13 @@ balsa_mime_widget_new_vcalendar(BalsaMessage * bm,
} \
} while (0)
-#define GRID_ATTACH_DATE(g,event,date_id,label) \
- G_STMT_START { \
- time_t _date = libbalsa_vevent_timestamp(event, date_id); \
- if (_date != (time_t) -1) { \
- gboolean _d_only = libbalsa_vevent_timestamp_date_only(event, date_id); \
- gchar * _dstr = \
- libbalsa_date_to_utf8(_date,
\
- _d_only ? "%x" : balsa_app.date_string); \
- GRID_ATTACH(g, _dstr, label); \
- g_free(_dstr); \
- } \
+#define GRID_ATTACH_DATE(g,event,date_id,label)
\
+ G_STMT_START { \
+ gchar *_dstr = libbalsa_vevent_time_str(event, date_id, balsa_app.date_string); \
+ if (_dstr != NULL) {
\
+ GRID_ATTACH(g, _dstr, label); \
+ g_free(_dstr); \
+ } \
} G_STMT_END
#define GRID_ATTACH_ADDRESS(g,addr,label) \
@@ -173,16 +177,33 @@ balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
int row = 0;
LibBalsaIdentity *vevent_ident = NULL;
guint attendee;
+ gchar *buffer;
GString *all_atts = NULL;
grid = GTK_GRID(gtk_grid_new());
gtk_grid_set_row_spacing(grid, 6);
gtk_grid_set_column_spacing(grid, 12);
GRID_ATTACH(grid, libbalsa_vevent_summary(event), _("Summary:"));
+ if (libbalsa_vevent_status(event) != ICAL_STATUS_NONE) {
+ GRID_ATTACH(grid, libbalsa_vevent_status_str(event), _("Status:"));
+ }
GRID_ATTACH_ADDRESS(grid, libbalsa_vevent_organizer(event), _("Organizer:"));
GRID_ATTACH_DATE(grid, event, VEVENT_DATETIME_STAMP, _("Created:"));
GRID_ATTACH_DATE(grid, event, VEVENT_DATETIME_START, _("Start:"));
GRID_ATTACH_DATE(grid, event, VEVENT_DATETIME_END, _("End:"));
+
+ buffer = libbalsa_vevent_duration_str(event);
+ if (buffer != NULL) {
+ GRID_ATTACH(grid, buffer, _("Duration:"));
+ g_free(buffer);
+ }
+
+ buffer = libbalsa_vevent_recurrence_str(event, balsa_app.date_string);
+ if (buffer != NULL) {
+ GRID_ATTACH(grid, buffer, _("Recurrence:"));
+ g_free(buffer);
+ }
+
GRID_ATTACH(grid, libbalsa_vevent_location(event), _("Location:"));
for (attendee = 0U; attendee < libbalsa_vevent_attendees(event); attendee++) {
LibBalsaAddress *lba = libbalsa_vevent_attendee(event, attendee);
@@ -215,6 +236,15 @@ balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
ngettext("Attendee:", "Attendees:", libbalsa_vevent_attendees(event)));
g_string_free(all_atts, TRUE);
}
+
+ if (libbalsa_vevent_category_count(event) > 0U) {
+ gchar *categories;
+
+ categories = libbalsa_vevent_category_str(event);
+ GRID_ATTACH(grid, categories, ngettext("Category:", "Categories:",
libbalsa_vevent_category_count(event)));
+ g_free(categories);
+ }
+
GRID_ATTACH_TEXT(grid, libbalsa_vevent_description(event), _("Description:"));
if (sender && vevent_ident) {
@@ -246,7 +276,7 @@ balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
g_object_set_data_full(G_OBJECT(button), "event", event,
(GDestroyNotify) g_object_unref);
g_object_set_data(G_OBJECT(button), "mode",
- GINT_TO_POINTER(VCAL_PSTAT_ACCEPTED));
+ GINT_TO_POINTER(ICAL_PARTSTAT_ACCEPTED));
g_signal_connect(button, "clicked",
G_CALLBACK(vevent_reply), bbox);
gtk_container_add(GTK_CONTAINER(bbox), button);
@@ -254,7 +284,7 @@ balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
button = gtk_button_new_with_label(_("Accept tentatively"));
g_object_set_data(G_OBJECT(button), "event", event);
g_object_set_data(G_OBJECT(button), "mode",
- GINT_TO_POINTER(VCAL_PSTAT_TENTATIVE));
+ GINT_TO_POINTER(ICAL_PARTSTAT_TENTATIVE));
g_signal_connect(button, "clicked",
G_CALLBACK(vevent_reply), bbox);
gtk_container_add(GTK_CONTAINER(bbox), button);
@@ -262,7 +292,7 @@ balsa_vevent_widget(LibBalsaVEvent * event, gboolean may_reply,
button = gtk_button_new_with_label(_("Decline"));
g_object_set_data(G_OBJECT(button), "event", event);
g_object_set_data(G_OBJECT(button), "mode",
- GINT_TO_POINTER(VCAL_PSTAT_DECLINED));
+ GINT_TO_POINTER(ICAL_PARTSTAT_DECLINED));
g_signal_connect(button, "clicked",
G_CALLBACK(vevent_reply), bbox);
gtk_container_add(GTK_CONTAINER(bbox), button);
@@ -277,7 +307,7 @@ vevent_reply(GObject * button, GtkWidget * box)
{
LibBalsaVEvent *event =
LIBBALSA_VEVENT(g_object_get_data(button, "event"));
- LibBalsaVCalPartStat pstat =
+ icalparameter_partstat pstat =
GPOINTER_TO_INT(g_object_get_data(button, "mode"));
gchar *rcpt;
LibBalsaMessage *message;
@@ -323,6 +353,7 @@ vevent_reply(GObject * button, GtkWidget * box)
libbalsa_vevent_reply(event,
INTERNET_ADDRESS_MAILBOX(ia)->addr,
pstat);
+ if (body->buffer == NULL) return;
body->charset = g_strdup("utf-8");
body->content_type = g_strdup("text/calendar");
libbalsa_message_append_part(message, body);
diff --git a/src/balsa-print-object-text.c b/src/balsa-print-object-text.c
index ca40a48a7..2a6a21392 100644
--- a/src/balsa-print-object-text.c
+++ b/src/balsa-print-object-text.c
@@ -458,6 +458,7 @@ balsa_print_object_text_vcard(GList *list,
result = balsa_print_object_default_full(list, context, pixbuf, textbuf, p_label_width, psetup);
g_object_unref(pixbuf);
g_free(textbuf);
+ g_object_unref(test_layout);
return result;
}
@@ -477,17 +478,13 @@ balsa_print_object_text_vcard(GList *list,
} \
} while(0)
-#define ADD_VCAL_DATE(buf, labwidth, layout, event, date_id, descr) \
- G_STMT_START { \
- time_t _date = libbalsa_vevent_timestamp(event, date_id); \
- if (_date != (time_t) -1) { \
- gboolean _d_only = libbalsa_vevent_timestamp_date_only(event, date_id); \
- gchar * _dstr = \
- libbalsa_date_to_utf8(_date,
\
- _d_only ? "%x" : balsa_app.date_string); \
- ADD_VCAL_FIELD(buf, labwidth, layout, _dstr, descr); \
- g_free(_dstr); \
- } \
+#define ADD_VCAL_DATE(buf, labwidth, layout, event, date_id, descr)
\
+ G_STMT_START { \
+ gchar *_dstr = libbalsa_vevent_time_str(event, date_id, balsa_app.date_string); \
+ if (_dstr != NULL) {
\
+ ADD_VCAL_FIELD(buf, labwidth, layout, _dstr, descr); \
+ g_free(_dstr); \
+ } \
} G_STMT_END
#define ADD_VCAL_ADDRESS(buf, labwidth, layout, addr, descr) \
@@ -533,22 +530,39 @@ balsa_print_object_text_calendar(GList *list,
pango_font_description_free(header_font);
/* add fields from the events*/
- desc_buf = g_string_new("");
+ desc_buf = g_string_new(NULL);
+ g_string_append_printf(desc_buf, _("This is an iTIP calendar “%s” message."),
libbalsa_vcal_method_str(vcal_obj));
p_label_width = 0;
- for (event_no = 0U; event_no < libbalsa_vcal_vevents(vcal_obj); event_no++) {
+ for (event_no = 0U; event_no < libbalsa_vcal_vevent_count(vcal_obj); event_no++) {
LibBalsaVEvent *event = libbalsa_vcal_vevent(vcal_obj, event_no);
+ gchar *buffer;
const gchar *description;
guint attendees;
- if (desc_buf->len > 0) {
- g_string_append_c(desc_buf, '\n');
- }
+ g_string_append_c(desc_buf, '\n');
ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, libbalsa_vevent_summary(event), _("Summary:"));
+ if (libbalsa_vevent_status(event) != ICAL_STATUS_NONE) {
+ ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, libbalsa_vevent_status_str(event),
_("Status:"));
+ }
ADD_VCAL_ADDRESS(desc_buf, p_label_width, test_layout, libbalsa_vevent_organizer(event),
_("Organizer:"));
ADD_VCAL_DATE(desc_buf, p_label_width, test_layout, event, VEVENT_DATETIME_STAMP, _("Created:"));
ADD_VCAL_DATE(desc_buf, p_label_width, test_layout, event, VEVENT_DATETIME_START, _("Start:"));
ADD_VCAL_DATE(desc_buf, p_label_width, test_layout, event, VEVENT_DATETIME_END, _("End:"));
+
+ buffer = libbalsa_vevent_duration_str(event);
+ if (buffer != NULL) {
+ ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, buffer, _("Duration:"));
+ g_free(buffer);
+ }
+
+ buffer = libbalsa_vevent_recurrence_str(event, balsa_app.date_string);
+ if (buffer != NULL) {
+ ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, buffer, _("Recurrence:"));
+ g_free(buffer);
+ }
+
ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, libbalsa_vevent_location(event),
_("Location:"));
+
attendees = libbalsa_vevent_attendees(event);
if (attendees > 0U) {
gchar *this_att;
@@ -564,6 +578,7 @@ balsa_print_object_text_calendar(GList *list,
g_free(this_att);
}
}
+
description = libbalsa_vevent_description(event);
if (description != NULL) {
gchar **desc_lines = g_strsplit(description, "\n", -1);
@@ -575,6 +590,21 @@ balsa_print_object_text_calendar(GList *list,
}
g_strfreev(desc_lines);
}
+
+ if (libbalsa_vevent_category_count(event) > 0U) {
+ gchar **cat_lines;
+ gint i;
+
+ buffer = libbalsa_vevent_category_str(event);
+ cat_lines = g_strsplit(buffer, "\n", -1);
+ g_free(buffer);
+ ADD_VCAL_FIELD(desc_buf, p_label_width, test_layout, cat_lines[0],
+ ngettext("Category:", "Categories:", libbalsa_vevent_category_count(event)));
+ for (i = 1; cat_lines[i]; i++) {
+ g_string_append_printf(desc_buf, "\n\t%s", cat_lines[i]);
+ }
+ g_strfreev(cat_lines);
+ }
}
g_object_unref(vcal_obj);
@@ -586,6 +616,7 @@ balsa_print_object_text_calendar(GList *list,
list = balsa_print_object_default_full(list, context, pixbuf, textbuf, p_label_width, psetup);
g_object_unref(pixbuf);
g_free(textbuf);
+ g_object_unref(test_layout);
return list;
}
diff --git a/src/print-gtk.c b/src/print-gtk.c
index d2ca1754a..57e11dc41 100644
--- a/src/print-gtk.c
+++ b/src/print-gtk.c
@@ -131,29 +131,29 @@ static LibBalsaMessageBody *
find_alt_part(LibBalsaMessageBody *parts,
gboolean print_alt_html)
{
- LibBalsaMessageBody *use_part;
+ LibBalsaMessageBody *part;
+ LibBalsaMessageBody *use_part = parts; /* fallback: print 1st alternative part */
/* scan the parts */
- for (use_part = parts; use_part != NULL; use_part = use_part->next) {
+ for (part = parts; part != NULL; part = part->next) {
gchar *mime_type;
- mime_type = libbalsa_message_body_get_mime_type(use_part);
- if ((strncmp(mime_type, "multipart/", 10U) == 0) && (use_part->parts != NULL)) {
+ mime_type = libbalsa_message_body_get_mime_type(part);
+ if ((strncmp(mime_type, "multipart/", 10U) == 0) && (part->parts != NULL)) {
/* consider the first child of a multipart */
g_free(mime_type);
- mime_type = libbalsa_message_body_get_mime_type(use_part->parts);
+ mime_type = libbalsa_message_body_get_mime_type(part->parts);
}
- if (((strcmp(mime_type, "text/plain") == 0) && !print_alt_html) ||
+ if ((strcmp(mime_type, "text/calendar") == 0) ||
+ ((strcmp(mime_type, "text/plain") == 0) && !print_alt_html) ||
((strcmp(mime_type, "text/html") == 0) && print_alt_html)) {
- g_free(mime_type);
- return use_part;
+ use_part = part;
}
g_free(mime_type);
}
- /* nothing found, fall back to the first part in the chain */
- return parts;
+ return use_part;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]