webcit_before_automake is now the trunk
[citadel.git] / webcit / availability.c
1 /*
2  * $Id$
3  */
4 /**
5  *
6  * \defgroup CalendarAv  Check attendee availability for scheduling a meeting.
7  * \ingroup Calendaring
8  */
9 /*@{*/
10
11
12 #include "webcit.h"
13 #include "webserver.h"
14
15 /** only available if we have calendaring */
16 #ifdef WEBCIT_WITH_CALENDAR_SERVICE
17
18
19
20 /**
21  * \brief verify users avaiability
22  * Utility function to fetch a VFREEBUSY type of thing for
23  * any specified user.
24  * \param who string of the user to search
25  */
26 icalcomponent *get_freebusy_for_user(char *who) {
27         char buf[SIZ];
28         char *serialized_fb = NULL;
29         icalcomponent *fb = NULL;
30
31         serv_printf("ICAL freebusy|%s", who);
32         serv_getln(buf, sizeof buf);
33         if (buf[0] == '1') {
34                 serialized_fb = read_server_text();
35         }
36
37         if (serialized_fb == NULL) {
38                 return NULL;
39         }
40         
41         fb = icalcomponent_new_from_string(serialized_fb);
42         free(serialized_fb);
43         if (fb == NULL) {
44                 return NULL;
45         }
46
47         return(fb);
48 }
49
50
51
52
53 /**
54  * \brief Check if dates are overlapping
55  * Check to see if two events overlap.  
56  * (This function is used in both Citadel and WebCit.  If you change it in
57  * one place, change it in the other.  Better yet, put it in a library.)
58  * \param t1start date one start
59  * \param t1end  date one end
60  * \param t2start date one start
61  * \param t2end date two end
62  * \returns nonzero if they do.
63  */
64 int ical_ctdl_is_overlap(
65                         struct icaltimetype t1start,
66                         struct icaltimetype t1end,
67                         struct icaltimetype t2start,
68                         struct icaltimetype t2end
69 ) {
70
71         if (icaltime_is_null_time(t1start)) return(0);
72         if (icaltime_is_null_time(t2start)) return(0);
73
74         /** First, check for all-day events */
75         if (t1start.is_date) {
76                 if (!icaltime_compare_date_only(t1start, t2start)) {
77                         return(1);
78                 }
79                 if (!icaltime_is_null_time(t2end)) {
80                         if (!icaltime_compare_date_only(t1start, t2end)) {
81                                 return(1);
82                         }
83                 }
84         }
85
86         if (t2start.is_date) {
87                 if (!icaltime_compare_date_only(t2start, t1start)) {
88                         return(1);
89                 }
90                 if (!icaltime_is_null_time(t1end)) {
91                         if (!icaltime_compare_date_only(t2start, t1end)) {
92                                 return(1);
93                         }
94                 }
95         }
96
97         /** Now check for overlaps using date *and* time. */
98
99         /** First, bail out if either event 1 or event 2 is missing end time. */
100         if (icaltime_is_null_time(t1end)) return(0);
101         if (icaltime_is_null_time(t2end)) return(0);
102
103         /** If event 1 ends before event 2 starts, we're in the clear. */
104         if (icaltime_compare(t1end, t2start) <= 0) return(0);
105
106         /** If event 2 ends before event 1 starts, we're also ok. */
107         if (icaltime_compare(t2end, t1start) <= 0) return(0);
108
109         /** Otherwise, they overlap. */
110         return(1);
111 }
112
113
114
115 /*
116  * \brief dig availability on citserver
117  * Back end function for check_attendee_availability()
118  * This one checks an individual attendee against a supplied
119  * event start and end time.  All these fields have already been
120  * broken out.  
121  * \param attendee_string name of the attendee
122  * \param event_start starttime of the event to check
123  * \param event_end endtime of the event to check
124  * \return The result is placed in 'annotation'.
125  */
126 void check_individual_attendee(char *attendee_string,
127                                 struct icaltimetype event_start,
128                                 struct icaltimetype event_end,
129                                 char *annotation) {
130
131         icalcomponent *fbc = NULL;
132         icalcomponent *fb = NULL;
133         icalproperty *thisfb = NULL;
134         struct icalperiodtype period;
135
136         /**
137          * Set to 'unknown' right from the beginning.  Unless we learn
138          * something else, that's what we'll go with.
139          */
140         strcpy(annotation, _("availability unknown"));
141
142         fbc = get_freebusy_for_user(attendee_string);
143         if (fbc == NULL) {
144                 return;
145         }
146
147         /**
148          * Make sure we're looking at a VFREEBUSY by itself.  What we're probably
149          * looking at initially is a VFREEBUSY encapsulated in a VCALENDAR.
150          */
151         if (icalcomponent_isa(fbc) == ICAL_VCALENDAR_COMPONENT) {
152                 fb = icalcomponent_get_first_component(fbc, ICAL_VFREEBUSY_COMPONENT);
153         }
154         else if (icalcomponent_isa(fbc) == ICAL_VFREEBUSY_COMPONENT) {
155                 fb = fbc;
156         }
157
158         /** Iterate through all FREEBUSY's looking for conflicts. */
159         if (fb != NULL) {
160
161                 strcpy(annotation, _("free"));
162
163                 for (thisfb = icalcomponent_get_first_property(fb, ICAL_FREEBUSY_PROPERTY);
164                     thisfb != NULL;
165                     thisfb = icalcomponent_get_next_property(fb, ICAL_FREEBUSY_PROPERTY) ) {
166
167                         /** Do the check */
168                         period = icalproperty_get_freebusy(thisfb);
169                         if (ical_ctdl_is_overlap(period.start, period.end,
170                            event_start, event_end)) {
171                                 strcpy(annotation, _("BUSY"));
172                         }
173
174                 }
175         }
176
177         icalcomponent_free(fbc);
178 }
179
180
181
182
183 /**
184  * \brief check attendees availability
185  * Check the availability of all attendees for an event (when possible)
186  * and annotate accordingly.
187  * \param vevent the event which should be compared with attendees calendar
188  */
189 void check_attendee_availability(icalcomponent *vevent) {
190         icalproperty *attendee = NULL;
191         icalproperty *dtstart_p = NULL;
192         icalproperty *dtend_p = NULL;
193         struct icaltimetype dtstart_t;
194         struct icaltimetype dtend_t;
195         char attendee_string[SIZ];
196         char annotated_attendee_string[SIZ];
197         char annotation[SIZ];
198
199         if (vevent == NULL) {
200                 return;
201         }
202
203         /**
204          * If we're looking at a fully encapsulated VCALENDAR
205          * rather than a VEVENT component, attempt to use the first
206          * relevant VEVENT subcomponent.  If there is none, the
207          * NULL returned by icalcomponent_get_first_component() will
208          * tell the next iteration of this function to create a
209          * new one.
210          */
211         if (icalcomponent_isa(vevent) == ICAL_VCALENDAR_COMPONENT) {
212                 check_attendee_availability(
213                         icalcomponent_get_first_component(
214                                 vevent, ICAL_VEVENT_COMPONENT
215                         )
216                 );
217                 return;
218         }
219
220         ical_dezonify(vevent);          /**< Convert everything to UTC */
221
222         /**
223          * Learn the start and end times.
224          */
225         dtstart_p = icalcomponent_get_first_property(vevent, ICAL_DTSTART_PROPERTY);
226         if (dtstart_p != NULL) dtstart_t = icalproperty_get_dtstart(dtstart_p);
227
228         dtend_p = icalcomponent_get_first_property(vevent, ICAL_DTEND_PROPERTY);
229         if (dtend_p != NULL) dtend_t = icalproperty_get_dtend(dtend_p);
230
231         /**
232          * Iterate through attendees.
233          */
234         for (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY);
235             attendee != NULL;
236             attendee = icalcomponent_get_next_property(vevent, ICAL_ATTENDEE_PROPERTY)) {
237
238                 strcpy(attendee_string, icalproperty_get_attendee(attendee));
239                 if (!strncasecmp(attendee_string, "MAILTO:", 7)) {
240
241                         /** screen name or email address */
242                         strcpy(attendee_string, &attendee_string[7]);
243                         striplt(attendee_string);
244
245                         check_individual_attendee(attendee_string,
246                                                 dtstart_t, dtend_t,
247                                                 annotation);
248
249                         /** Replace the attendee name with an annotated one. */
250                         snprintf(annotated_attendee_string, sizeof annotated_attendee_string,
251                                 "MAILTO:%s (%s)", attendee_string, annotation);
252                         icalproperty_set_attendee(attendee, annotated_attendee_string);
253
254                 }
255         }
256
257 }
258
259
260 #endif /* WEBCIT_WITH_CALENDAR_SERVICE */
261
262 /** @} */