1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* Copyright (C) 2002-2004 Novell, Inc.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 2 of the GNU Lesser General Public
7 * License as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
20 /* e2k-freebusy.c: routines for manipulating Exchange free/busy data */
26 #include "e2k-freebusy.h"
27 #include "e2k-propnames.h"
28 #include "e2k-restriction.h"
30 #include "e2k-utils.h"
35 #include <libedataserver/e-time-utils.h>
38 * e2k_freebusy_destroy:
39 * @fb: the #E2kFreebusy
41 * Frees @fb and all associated data.
44 e2k_freebusy_destroy (E2kFreebusy *fb)
48 g_object_unref (fb->ctx);
49 for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
50 g_array_free (fb->events[i], TRUE);
57 fb_uri_for_dn (const char *public_uri, const char *dn)
59 char *uri, *div, *org;
62 for (div = strchr (dn, '/'); div; div = strchr (div + 1, '/')) {
63 if (!g_ascii_strncasecmp (div, "/cn=", 4))
66 g_return_val_if_fail (div, NULL);
68 org = g_strndup (dn, div - dn);
70 str = g_string_new (public_uri);
71 g_string_append (str, "/NON_IPM_SUBTREE/SCHEDULE%2B%20FREE%20BUSY/EX:");
72 e2k_uri_append_encoded (str, org, TRUE, NULL);
73 g_string_append (str, "/USER-");
74 e2k_uri_append_encoded (str, div, TRUE, NULL);
75 g_string_append (str, ".EML");
78 g_string_free (str, FALSE);
85 merge_events (GArray *events)
87 E2kFreebusyEvent evt, evt2;
93 evt = g_array_index (events, E2kFreebusyEvent, 0);
94 for (i = 1; i < events->len; i++) {
95 evt2 = g_array_index (events, E2kFreebusyEvent, i);
96 if (evt.end >= evt2.start) {
97 if (evt2.end > evt.end)
99 g_array_remove_index (events, i--);
106 add_data_for_status (E2kFreebusy *fb, GPtrArray *monthyears, GPtrArray *fbdatas, GArray *events)
108 E2kFreebusyEvent evt;
114 if (!monthyears || !fbdatas)
117 memset (&tm, 0, sizeof (tm));
118 for (i = 0; i < monthyears->len && i < fbdatas->len; i++) {
119 monthyear = atoi (monthyears->pdata[i]);
120 fbdata = fbdatas->pdata[i];
122 tm.tm_year = (monthyear >> 4) - 1900;
123 tm.tm_mon = (monthyear & 0xF) - 1;
125 for (p = fbdata->data; p + 3 < fbdata->data + fbdata->len; p += 4) {
128 tm.tm_min = p[0] + p[1] * 256;
129 evt.start = e_mktime_utc (&tm);
133 tm.tm_min = p[2] + p[3] * 256;
134 evt.end = e_mktime_utc (&tm);
136 g_array_append_val (events, evt);
139 merge_events (events);
142 static const char *public_freebusy_props[] = {
143 PR_FREEBUSY_START_RANGE,
144 PR_FREEBUSY_END_RANGE,
145 PR_FREEBUSY_ALL_MONTHS,
146 PR_FREEBUSY_ALL_EVENTS,
147 PR_FREEBUSY_TENTATIVE_MONTHS,
148 PR_FREEBUSY_TENTATIVE_EVENTS,
149 PR_FREEBUSY_BUSY_MONTHS,
150 PR_FREEBUSY_BUSY_EVENTS,
151 PR_FREEBUSY_OOF_MONTHS,
152 PR_FREEBUSY_OOF_EVENTS
154 static const int n_public_freebusy_props = sizeof (public_freebusy_props) / sizeof (public_freebusy_props[0]);
158 * @ctx: an #E2kContext
159 * @public_uri: the URI of the MAPI public folder tree
160 * @dn: the legacy Exchange DN of a user
162 * Creates a new #E2kFreebusy, filled in with information from the
163 * indicated user's published free/busy information. This uses the
164 * public free/busy folder; the caller does not need permission to
165 * access the @dn's Calendar.
167 * Note that currently, this will fail and return %NULL if the user
168 * does not already have free/busy information stored on the server.
170 * Return value: the freebusy information
173 e2k_freebusy_new (E2kContext *ctx, const char *public_uri, const char *dn)
177 GPtrArray *monthyears, *fbdatas;
178 E2kHTTPStatus status;
182 uri = fb_uri_for_dn (public_uri, dn);
183 g_return_val_if_fail (uri, NULL);
185 status = e2k_context_propfind (ctx, NULL, uri,
186 public_freebusy_props,
187 n_public_freebusy_props,
188 &results, &nresults);
189 if (!E2K_HTTP_STATUS_IS_SUCCESSFUL (status) || nresults == 0) {
190 /* FIXME: create it */
195 fb = g_new0 (E2kFreebusy, 1);
197 fb->dn = g_strdup (dn);
201 for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
202 fb->events[i] = g_array_new (FALSE, FALSE, sizeof (E2kFreebusyEvent));
204 time = e2k_properties_get_prop (
205 results[0].props, PR_FREEBUSY_START_RANGE);
206 fb->start = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
207 time = e2k_properties_get_prop (
208 results[0].props, PR_FREEBUSY_END_RANGE);
209 fb->end = time ? e2k_systime_to_time_t (strtol (time, NULL, 10)) : 0;
211 monthyears = e2k_properties_get_prop (
212 results[0].props, PR_FREEBUSY_ALL_MONTHS);
213 fbdatas = e2k_properties_get_prop (
214 results[0].props, PR_FREEBUSY_ALL_EVENTS);
215 add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_ALL]);
217 monthyears = e2k_properties_get_prop (
218 results[0].props, PR_FREEBUSY_TENTATIVE_MONTHS);
219 fbdatas = e2k_properties_get_prop (
220 results[0].props, PR_FREEBUSY_TENTATIVE_EVENTS);
221 add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_TENTATIVE]);
223 monthyears = e2k_properties_get_prop (
224 results[0].props, PR_FREEBUSY_BUSY_MONTHS);
225 fbdatas = e2k_properties_get_prop (
226 results[0].props, PR_FREEBUSY_BUSY_EVENTS);
227 add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_BUSY]);
229 monthyears = e2k_properties_get_prop (
230 results[0].props, PR_FREEBUSY_OOF_MONTHS);
231 fbdatas = e2k_properties_get_prop (
232 results[0].props, PR_FREEBUSY_OOF_EVENTS);
233 add_data_for_status (fb, monthyears, fbdatas, fb->events[E2K_BUSYSTATUS_OOF]);
235 e2k_results_free (results, nresults);
240 * e2k_freebusy_reset:
241 * @fb: an #E2kFreebusy
242 * @nmonths: the number of months of info @fb will store
244 * Clears all existing data in @fb and resets the start and end times
245 * to a span of @nmonths around the current date.
248 e2k_freebusy_reset (E2kFreebusy *fb, int nmonths)
254 /* Remove all existing events */
255 for (i = 0; i < E2K_BUSYSTATUS_MAX; i++)
256 g_array_set_size (fb->events[i], 0);
258 /* Set the start and end times appropriately: from the beginning
259 * of the current month until nmonths later.
260 * FIXME: Use default timezone, not local time.
265 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
268 fb->start = mktime (&tm);
270 tm.tm_mon += nmonths;
272 fb->end = mktime (&tm);
276 * e2k_freebusy_add_interval:
277 * @fb: an #E2kFreebusy
278 * @busystatus: the busy status of the interval
279 * @start: the start of the interval
280 * @end: the end of the interval
282 * This adds an interval of type @busystatus to @fb.
285 e2k_freebusy_add_interval (E2kFreebusy *fb, E2kBusyStatus busystatus,
286 time_t start, time_t end)
288 E2kFreebusyEvent evt, *events;
291 if (busystatus == E2K_BUSYSTATUS_FREE)
294 /* Clip to the fb's range */
295 if (start < fb->start)
302 events = (E2kFreebusyEvent *)(fb->events[busystatus]->data);
304 for (i = 0; i < fb->events[busystatus]->len; i++) {
305 if (events[i].end >= start)
312 if (i == fb->events[busystatus]->len)
313 g_array_append_val (fb->events[busystatus], evt);
315 /* events[i] is the first event that is not completely
316 * before evt, meaning it is either completely after it,
317 * or they overlap/abut.
319 if (events[i].start > end) {
320 /* No overlap. Insert evt before events[i]. */
321 g_array_insert_val (fb->events[busystatus], i, evt);
323 /* They overlap or abut. Merge them. */
324 events[i].start = MIN (events[i].start, start);
325 events[i].end = MAX (events[i].end, end);
331 * e2k_freebusy_clear_interval:
332 * @fb: an #E2kFreebusy
333 * @start: the start of the interval
334 * @end: the end of the interval
336 * This removes any events between @start and @end in @fb.
339 e2k_freebusy_clear_interval (E2kFreebusy *fb, time_t start, time_t end)
341 E2kFreebusyEvent *evt;
344 for (busystatus = 0; busystatus < E2K_BUSYSTATUS_MAX; busystatus++) {
345 for (i = 0; i < fb->events[busystatus]->len; i++) {
346 evt = &g_array_index (fb->events[busystatus], E2kFreebusyEvent, i);
347 if (evt->end < start || evt->start > end)
350 /* evt overlaps the interval. Truncate or
354 if (evt->start > start /* && evt->start <= end */)
356 if (evt->end < end /* && evt->end >= start */)
359 if (evt->start >= evt->end)
360 g_array_remove_index (fb->events[busystatus], i--);
365 static const char *freebusy_props[] = {
366 E2K_PR_CALENDAR_DTSTART,
367 E2K_PR_CALENDAR_DTEND,
368 E2K_PR_CALENDAR_BUSY_STATUS
370 static const int n_freebusy_props = sizeof (freebusy_props) / sizeof (freebusy_props[0]);
373 * e2k_freebusy_add_from_calendar_uri:
374 * @fb: an #E2kFreebusy
375 * @uri: the URI of a calendar folder
376 * @start_tt: start of the range to add
377 * @end_tt: end of the range to add
379 * This queries the server for events between @start_tt and @end_tt in
380 * the calendar at @uri (which the caller must have permission to
381 * read) and adds them @fb. Any previously-existing events during that
384 * Return value: an HTTP status code.
387 e2k_freebusy_add_from_calendar_uri (E2kFreebusy *fb, const char *uri,
388 time_t start_tt, time_t end_tt)
390 char *start, *end, *busystatus;
396 e2k_freebusy_clear_interval (fb, start_tt, end_tt);
398 start = e2k_make_timestamp (start_tt);
399 end = e2k_make_timestamp (end_tt);
401 rn = e2k_restriction_andv (
402 e2k_restriction_prop_string (E2K_PR_DAV_CONTENT_CLASS,
404 "urn:content-classes:appointment"),
405 e2k_restriction_prop_date (E2K_PR_CALENDAR_DTEND,
406 E2K_RELOP_GT, start),
407 e2k_restriction_prop_date (E2K_PR_CALENDAR_DTSTART,
409 e2k_restriction_prop_string (E2K_PR_CALENDAR_BUSY_STATUS,
410 E2K_RELOP_NE, "FREE"),
413 iter = e2k_context_search_start (fb->ctx, NULL, uri,
414 freebusy_props, n_freebusy_props,
416 e2k_restriction_unref (rn);
420 while ((result = e2k_result_iter_next (iter))) {
421 start = e2k_properties_get_prop (result->props,
422 E2K_PR_CALENDAR_DTSTART);
423 end = e2k_properties_get_prop (result->props,
424 E2K_PR_CALENDAR_DTEND);
425 busystatus = e2k_properties_get_prop (result->props,
426 E2K_PR_CALENDAR_BUSY_STATUS);
427 if (!start || !end || !busystatus)
430 if (!strcmp (busystatus, "TENTATIVE"))
431 busy = E2K_BUSYSTATUS_TENTATIVE;
432 else if (!strcmp (busystatus, "OUTOFOFFICE"))
433 busy = E2K_BUSYSTATUS_OOF;
435 busy = E2K_BUSYSTATUS_BUSY;
437 e2k_freebusy_add_interval (fb, busy,
438 e2k_parse_timestamp (start),
439 e2k_parse_timestamp (end));
443 return e2k_result_iter_free (iter);
447 add_events (GArray *events_array, E2kProperties *props,
448 const char *month_list_prop, const char *data_list_prop)
450 E2kFreebusyEvent *events = (E2kFreebusyEvent *)events_array->data;
451 int i, evt_start, evt_end, monthyear;
452 struct tm start_tm, end_tm;
454 GPtrArray *monthyears, *datas;
458 if (!events_array->len) {
459 e2k_properties_remove (props, month_list_prop);
460 e2k_properties_remove (props, data_list_prop);
464 monthyears = g_ptr_array_new ();
465 start_tm = *gmtime (&events[0].start);
466 end_tm = *gmtime (&events[events_array->len - 1].end);
467 while (start_tm.tm_year <= end_tm.tm_year ||
468 start_tm.tm_mon <= end_tm.tm_mon) {
469 monthyear = ((start_tm.tm_year + 1900) * 16) +
470 (start_tm.tm_mon + 1);
471 g_ptr_array_add (monthyears, g_strdup_printf ("%d", monthyear));
474 if (start_tm.tm_mon == 12) {
479 e2k_properties_set_int_array (props, month_list_prop, monthyears);
481 datas = g_ptr_array_new ();
482 start = events[0].start;
484 while (i < events_array->len) {
485 start_tm = *gmtime (&start);
487 end = e_mktime_utc (&start_tm);
489 data = g_byte_array_new ();
490 while (i << events_array->len &&
491 events[i].end > start && events[i].start < end) {
492 if (events[i].start < start)
495 evt_start = (events[i].start - start) / 60;
496 if (events[i].end > end)
497 evt_end = (end - start) / 60;
499 evt_end = (events[i].end - start) / 60;
501 startend[0] = evt_start & 0xFF;
502 startend[1] = evt_start >> 8;
503 startend[2] = evt_end & 0xFF;
504 startend[3] = evt_end >> 8;
505 g_byte_array_append (data, startend, 4);
509 g_ptr_array_add (datas, data);
512 e2k_properties_set_binary_array (props, data_list_prop, datas);
517 * @fb: an #E2kFreebusy
519 * Saves the data in @fb back to the server.
521 * Return value: a libsoup or HTTP status code
524 e2k_freebusy_save (E2kFreebusy *fb)
526 E2kProperties *props;
528 E2kHTTPStatus status;
530 props = e2k_properties_new ();
531 e2k_properties_set_string (props, E2K_PR_EXCHANGE_MESSAGE_CLASS,
532 g_strdup ("IPM.Post"));
533 e2k_properties_set_int (props, PR_FREEBUSY_START_RANGE, fb->start);
534 e2k_properties_set_int (props, PR_FREEBUSY_END_RANGE, fb->end);
535 e2k_properties_set_string (props, PR_FREEBUSY_EMAIL_ADDRESS,
538 add_events (fb->events[E2K_BUSYSTATUS_ALL], props,
539 PR_FREEBUSY_ALL_MONTHS, PR_FREEBUSY_ALL_EVENTS);
540 add_events (fb->events[E2K_BUSYSTATUS_TENTATIVE], props,
541 PR_FREEBUSY_TENTATIVE_MONTHS, PR_FREEBUSY_TENTATIVE_EVENTS);
542 add_events (fb->events[E2K_BUSYSTATUS_BUSY], props,
543 PR_FREEBUSY_BUSY_MONTHS, PR_FREEBUSY_BUSY_EVENTS);
544 add_events (fb->events[E2K_BUSYSTATUS_OOF], props,
545 PR_FREEBUSY_OOF_MONTHS, PR_FREEBUSY_OOF_EVENTS);
547 timestamp = e2k_make_timestamp (e2k_context_get_last_timestamp (fb->ctx));
548 e2k_properties_set_date (props, PR_FREEBUSY_LAST_MODIFIED, timestamp);
550 status = e2k_context_proppatch (fb->ctx, NULL, fb->uri, props,
552 e2k_properties_free (props);