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 gchar *public_uri, const gchar *dn)
59
gchar *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 gchar *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 gint 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 gchar *public_uri, const gchar *dn)
177
GPtrArray *monthyears, *fbdatas;
178
E2kHTTPStatus status;
180
gint nresults = 0, i;
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, gint 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 gchar *freebusy_props[] = {
366
E2K_PR_CALENDAR_DTSTART,
367
E2K_PR_CALENDAR_DTEND,
368
E2K_PR_CALENDAR_BUSY_STATUS
370
static const gint 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 gchar *uri,
388
time_t start_tt, time_t end_tt)
390
gchar *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 gchar *month_list_prop, const gchar *data_list_prop)
450
E2kFreebusyEvent *events = (E2kFreebusyEvent *)events_array->data;
451
gint 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, (guint8 *) 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);