core cleanup: start cleanup up status handling
[siplcs.git] / src / core / sipe-cal.c
blob01d638963b5a91c132ca03d241218d34bf5e2944
1 /**
2 * @file sipe-cal.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
33 #include <glib.h>
35 #include "http-conn.h"
36 #include "sipe-backend.h"
37 #include "sipe-buddy.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-cal.h"
41 #include "sipe-nls.h"
42 #include "sipe-ocs2005.h"
43 #include "sipe-ocs2007.h"
44 #include "sipe-schedule.h"
45 #include "sipe-utils.h"
46 #include "sipe-xml.h"
47 #include "sipe.h"
49 /* Calendar backends */
50 #ifdef _WIN32
51 #include "sipe-domino.h"
52 #endif
53 #include "sipe-ews.h"
55 #define TIME_NULL (time_t)-1
56 #define IS(time) (time != TIME_NULL)
59 http://msdn.microsoft.com/en-us/library/aa565001.aspx
61 <?xml version="1.0"?>
62 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
63 <TimeZone>
64 <Bias>480</Bias>
65 <StandardTime>
66 <Bias>0</Bias>
67 <Time>02:00:00</Time>
68 <DayOrder>1</DayOrder>
69 <Month>11</Month>
70 <DayOfWeek>Sunday</DayOfWeek>
71 </StandardTime>
72 <DaylightTime>
73 <Bias>-60</Bias>
74 <Time>02:00:00</Time>
75 <DayOrder>2</DayOrder>
76 <Month>3</Month>
77 <DayOfWeek>Sunday</DayOfWeek>
78 </DaylightTime>
79 </TimeZone>
80 <WorkingPeriodArray>
81 <WorkingPeriod>
82 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
83 <StartTimeInMinutes>600</StartTimeInMinutes>
84 <EndTimeInMinutes>1140</EndTimeInMinutes>
85 </WorkingPeriod>
86 </WorkingPeriodArray>
87 </WorkingHours>
89 Desc:
90 <StandardTime>
91 <Bias>int</Bias>
92 <Time>string</Time>
93 <DayOrder>short</DayOrder>
94 <Month>short</Month>
95 <DayOfWeek>Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday</DayOfWeek>
96 <Year>string</Year>
97 </StandardTime>
100 struct sipe_cal_std_dst {
101 int bias; /* Ex.: -60 */
102 gchar *time; /* hh:mm:ss, 02:00:00 */
103 int day_order; /* 1..5 */
104 int month; /* 1..12 */
105 gchar *day_of_week; /* Sunday or Monday or Tuesday or Wednesday or Thursday or Friday or Saturday */
106 gchar *year; /* YYYY */
108 time_t switch_time;
111 struct sipe_cal_working_hours {
112 int bias; /* Ex.: 480 */
113 struct sipe_cal_std_dst std; /* StandardTime */
114 struct sipe_cal_std_dst dst; /* DaylightTime */
115 gchar *days_of_week; /* Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday separated by space */
116 int start_time; /* 0...1440 */
117 int end_time; /* 0...1440 */
119 gchar *tz; /* aggregated timezone string as in TZ environment variable.
120 Ex.: TST+8TDT+7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
121 /** separate simple strings for Windows platform as the proper TZ does not work there.
122 * anyway, dynamic timezones would't work with just TZ
124 gchar *tz_std; /* Ex.: TST8 */
125 gchar *tz_dst; /* Ex.: TDT7 */
128 /* not for translation, a part of XML Schema definitions */
129 static const char *wday_names[] = {"Sunday",
130 "Monday",
131 "Tuesday",
132 "Wednesday",
133 "Thursday",
134 "Friday",
135 "Saturday"};
136 static int
137 sipe_cal_get_wday(char *wday_name)
139 int i;
141 if (!wday_name) return -1;
143 for (i = 0; i < 7; i++) {
144 if (sipe_strequal(wday_names[i], wday_name)) {
145 return i;
149 return -1;
152 void
153 sipe_cal_event_free(struct sipe_cal_event* cal_event)
155 if (!cal_event) return;
157 g_free(cal_event->subject);
158 g_free(cal_event->location);
159 g_free(cal_event);
162 void
163 sipe_cal_events_free(GSList *cal_events)
165 GSList *entry = cal_events;
167 if (!cal_events) return;
169 while (entry) {
170 struct sipe_cal_event *cal_event = entry->data;
171 sipe_cal_event_free(cal_event);
172 entry = entry->next;
175 g_slist_free(cal_events);
178 void
179 sipe_cal_calendar_free(struct sipe_calendar *cal)
181 g_free(cal->email);
182 g_free(cal->legacy_dn);
183 if (cal->auth) {
184 g_free(cal->auth->domain);
185 g_free(cal->auth->user);
186 g_free(cal->auth->password);
188 g_free(cal->auth);
189 g_free(cal->as_url);
190 g_free(cal->oof_url);
191 g_free(cal->oab_url);
192 g_free(cal->domino_url);
193 g_free(cal->oof_state);
194 g_free(cal->oof_note);
195 g_free(cal->free_busy);
196 g_free(cal->working_hours_xml_str);
198 sipe_cal_events_free(cal->cal_events);
200 if (cal->http_conn) {
201 http_conn_free(cal->http_conn);
204 if (cal->http_session) {
205 http_conn_session_free(cal->http_session);
208 g_free(cal);
211 gboolean
212 sipe_cal_calendar_init(struct sipe_core_private *sipe_private,
213 gboolean *has_url)
215 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
216 if (!sip->cal) {
217 const char *value;
219 sip->cal = g_new0(struct sipe_calendar, 1);
220 sip->cal->sipe_private = sipe_private;
222 sip->cal->email = g_strdup(sip->email);
224 /* user specified a service URL? */
225 value = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
226 if (has_url) *has_url = !is_empty(value);
227 if (!is_empty(value)) {
228 sip->cal->as_url = g_strdup(value);
229 sip->cal->oof_url = g_strdup(value);
230 sip->cal->domino_url = g_strdup(value);
233 sip->cal->auth = g_new0(HttpConnAuth, 1);
234 sip->cal->auth->use_negotiate = SIPE_CORE_PUBLIC_FLAG_IS(KRB5);
236 /* user specified email login? */
237 value = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_LOGIN);
238 if (!is_empty(value)) {
240 /* user specified email login domain? */
241 const char *tmp = strstr(value, "\\");
242 if (tmp) {
243 sip->cal->auth->domain = g_strndup(value, tmp - value);
244 sip->cal->auth->user = g_strdup(tmp + 1);
245 } else {
246 sip->cal->auth->user = g_strdup(value);
248 sip->cal->auth->password = g_strdup(sipe_backend_setting(SIPE_CORE_PUBLIC,
249 SIPE_SETTING_EMAIL_PASSWORD));
251 } else {
252 /* re-use SIPE credentials */
253 sip->cal->auth->domain = g_strdup(sip->authdomain);
254 sip->cal->auth->user = g_strdup(sip->authuser);
255 sip->cal->auth->password = g_strdup(sip->password);
257 return TRUE;
259 return FALSE;
263 char *
264 sipe_cal_event_describe(struct sipe_cal_event* cal_event)
266 GString* str = g_string_new(NULL);
267 const char *status = "";
269 switch(cal_event->cal_status) {
270 case SIPE_CAL_FREE: status = "SIPE_CAL_FREE"; break;
271 case SIPE_CAL_TENTATIVE: status = "SIPE_CAL_TENTATIVE"; break;
272 case SIPE_CAL_BUSY: status = "SIPE_CAL_BUSY"; break;
273 case SIPE_CAL_OOF: status = "SIPE_CAL_OOF"; break;
274 case SIPE_CAL_NO_DATA: status = "SIPE_CAL_NO_DATA"; break;
277 g_string_append_printf(str, "\t%s: %s", "start_time",
278 IS(cal_event->start_time) ? asctime(localtime(&cal_event->start_time)) : "\n");
279 g_string_append_printf(str, "\t%s: %s", "end_time ",
280 IS(cal_event->end_time) ? asctime(localtime(&cal_event->end_time)) : "\n");
281 g_string_append_printf(str, "\t%s: %s\n", "cal_status", status);
282 g_string_append_printf(str, "\t%s: %s\n", "subject ", cal_event->subject ? cal_event->subject : "");
283 g_string_append_printf(str, "\t%s: %s\n", "location ", cal_event->location ? cal_event->location : "");
284 g_string_append_printf(str, "\t%s: %s\n", "is_meeting", cal_event->is_meeting ? "TRUE" : "FALSE");
286 return g_string_free(str, FALSE);
289 char *
290 sipe_cal_event_hash(struct sipe_cal_event* event)
292 /* no end_time as it dos not get published */
293 /* no cal_status as it can change on publication */
294 return g_strdup_printf("<%d><%s><%s><%d>",
295 (int)event->start_time,
296 event->subject ? event->subject : "",
297 event->location ? event->location : "",
298 event->is_meeting);
301 #define ENVIRONMENT_TIMEZONE "TZ"
303 static gchar *
304 sipe_switch_tz(const char *tz)
306 gchar *tz_orig;
308 tz_orig = g_strdup(g_getenv(ENVIRONMENT_TIMEZONE));
309 g_setenv(ENVIRONMENT_TIMEZONE, tz, TRUE);
310 tzset();
311 return(tz_orig);
314 static void
315 sipe_reset_tz(gchar *tz_orig)
317 if (tz_orig) {
318 g_setenv(ENVIRONMENT_TIMEZONE, tz_orig, TRUE);
319 g_free(tz_orig);
320 } else {
321 g_unsetenv(ENVIRONMENT_TIMEZONE);
323 tzset();
327 * Converts struct tm to Epoch time_t considering timezone.
329 * @param tz as defined for TZ environment variable.
331 * Reference: see timegm(3) - Linux man page
333 time_t
334 sipe_mktime_tz(struct tm *tm,
335 const char* tz)
337 time_t ret;
338 gchar *tz_orig;
340 tz_orig = sipe_switch_tz(tz);
341 ret = mktime(tm);
342 sipe_reset_tz(tz_orig);
344 return ret;
348 * Converts Epoch time_t to struct tm considering timezone.
350 * @param tz as defined for TZ environment variable.
352 * Reference: see timegm(3) - Linux man page
354 static struct tm *
355 sipe_localtime_tz(const time_t *time,
356 const char* tz)
358 struct tm *ret;
359 gchar *tz_orig;
361 tz_orig = sipe_switch_tz(tz);
362 ret = localtime(time);
363 sipe_reset_tz(tz_orig);
365 return ret;
368 void
369 sipe_cal_free_working_hours(struct sipe_cal_working_hours *wh)
371 if (!wh) return;
373 g_free(wh->std.time);
374 g_free(wh->std.day_of_week);
375 g_free(wh->std.year);
377 g_free(wh->dst.time);
378 g_free(wh->dst.day_of_week);
379 g_free(wh->dst.year);
381 g_free(wh->days_of_week);
382 g_free(wh->tz);
383 g_free(wh->tz_std);
384 g_free(wh->tz_dst);
385 g_free(wh);
389 * Returns time_t of daylight savings time start/end
390 * in the provided timezone or otherwise
391 * (time_t)-1 if no daylight savings time.
393 static time_t
394 sipe_cal_get_std_dst_time(time_t now,
395 int bias,
396 struct sipe_cal_std_dst* std_dst,
397 struct sipe_cal_std_dst* dst_std)
399 struct tm switch_tm;
400 time_t res = TIME_NULL;
401 struct tm *gm_now_tm;
402 gchar **time_arr;
404 if (std_dst->month == 0) return TIME_NULL;
406 gm_now_tm = gmtime(&now);
407 time_arr = g_strsplit(std_dst->time, ":", 0);
409 switch_tm.tm_sec = atoi(time_arr[2]);
410 switch_tm.tm_min = atoi(time_arr[1]);
411 switch_tm.tm_hour = atoi(time_arr[0]);
412 g_strfreev(time_arr);
413 switch_tm.tm_mday = std_dst->year ? std_dst->day_order : 1 /* to adjust later */ ;
414 switch_tm.tm_mon = std_dst->month - 1;
415 switch_tm.tm_year = std_dst->year ? atoi(std_dst->year) - 1900 : gm_now_tm->tm_year;
416 switch_tm.tm_isdst = 0;
417 /* to set tm_wday */
418 res = sipe_mktime_tz(&switch_tm, "UTC");
420 /* if not dynamic, calculate right tm_mday */
421 if (!std_dst->year) {
422 int switch_wday = sipe_cal_get_wday(std_dst->day_of_week);
423 int needed_month;
424 /* get first desired wday in the month */
425 int delta = switch_wday >= switch_tm.tm_wday ? (switch_wday - switch_tm.tm_wday) : (switch_wday + 7 - switch_tm.tm_wday);
426 switch_tm.tm_mday = 1 + delta;
427 /* try nth order */
428 switch_tm.tm_mday += (std_dst->day_order - 1) * 7;
429 needed_month = switch_tm.tm_mon;
430 /* to set settle date if ahead of allowed month dates */
431 res = sipe_mktime_tz(&switch_tm, "UTC");
432 if (needed_month != switch_tm.tm_mon) {
433 /* moving 1 week back to stay within required month */
434 switch_tm.tm_mday -= 7;
435 /* to fix date again */
436 res = sipe_mktime_tz(&switch_tm, "UTC");
439 /* note: bias is taken from "switch to" structure */
440 return res + (bias + dst_std->bias)*60;
443 static void
444 sipe_cal_parse_std_dst(const sipe_xml *xn_std_dst_time,
445 struct sipe_cal_std_dst *std_dst)
447 const sipe_xml *node;
448 gchar *tmp;
450 if (!xn_std_dst_time) return;
451 if (!std_dst) return;
453 <StandardTime>
454 <Bias>0</Bias>
455 <Time>02:00:00</Time>
456 <DayOrder>1</DayOrder>
457 <Month>11</Month>
458 <DayOfWeek>Sunday</DayOfWeek>
459 </StandardTime>
462 if ((node = sipe_xml_child(xn_std_dst_time, "Bias"))) {
463 std_dst->bias = atoi(tmp = sipe_xml_data(node));
464 g_free(tmp);
467 if ((node = sipe_xml_child(xn_std_dst_time, "Time"))) {
468 std_dst->time = sipe_xml_data(node);
471 if ((node = sipe_xml_child(xn_std_dst_time, "DayOrder"))) {
472 std_dst->day_order = atoi(tmp = sipe_xml_data(node));
473 g_free(tmp);
476 if ((node = sipe_xml_child(xn_std_dst_time, "Month"))) {
477 std_dst->month = atoi(tmp = sipe_xml_data(node));
478 g_free(tmp);
481 if ((node = sipe_xml_child(xn_std_dst_time, "DayOfWeek"))) {
482 std_dst->day_of_week = sipe_xml_data(node);
485 if ((node = sipe_xml_child(xn_std_dst_time, "Year"))) {
486 std_dst->year = sipe_xml_data(node);
490 void
491 sipe_cal_parse_working_hours(const sipe_xml *xn_working_hours,
492 struct sipe_buddy *buddy)
494 const sipe_xml *xn_bias;
495 const sipe_xml *xn_timezone;
496 const sipe_xml *xn_working_period;
497 const sipe_xml *xn_standard_time;
498 const sipe_xml *xn_daylight_time;
499 gchar *tmp;
500 time_t now = time(NULL);
501 struct sipe_cal_std_dst* std;
502 struct sipe_cal_std_dst* dst;
504 if (!xn_working_hours) return;
506 <WorkingHours xmlns="http://schemas.microsoft.com/exchange/services/2006/types">
507 <TimeZone>
508 <Bias>480</Bias>
510 </TimeZone>
511 <WorkingPeriodArray>
512 <WorkingPeriod>
513 <DayOfWeek>Monday Tuesday Wednesday Thursday Friday</DayOfWeek>
514 <StartTimeInMinutes>600</StartTimeInMinutes>
515 <EndTimeInMinutes>1140</EndTimeInMinutes>
516 </WorkingPeriod>
517 </WorkingPeriodArray>
518 </WorkingHours>
520 sipe_cal_free_working_hours(buddy->cal_working_hours);
521 buddy->cal_working_hours = g_new0(struct sipe_cal_working_hours, 1);
523 xn_timezone = sipe_xml_child(xn_working_hours, "TimeZone");
524 xn_bias = sipe_xml_child(xn_timezone, "Bias");
525 if (xn_bias) {
526 buddy->cal_working_hours->bias = atoi(tmp = sipe_xml_data(xn_bias));
527 g_free(tmp);
530 xn_standard_time = sipe_xml_child(xn_timezone, "StandardTime");
531 xn_daylight_time = sipe_xml_child(xn_timezone, "DaylightTime");
533 std = &((*buddy->cal_working_hours).std);
534 dst = &((*buddy->cal_working_hours).dst);
535 sipe_cal_parse_std_dst(xn_standard_time, std);
536 sipe_cal_parse_std_dst(xn_daylight_time, dst);
538 xn_working_period = sipe_xml_child(xn_working_hours, "WorkingPeriodArray/WorkingPeriod");
539 if (xn_working_period) {
540 /* NOTE: this can be NULL! */
541 buddy->cal_working_hours->days_of_week =
542 sipe_xml_data(sipe_xml_child(xn_working_period, "DayOfWeek"));
544 buddy->cal_working_hours->start_time =
545 atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "StartTimeInMinutes")));
546 g_free(tmp);
548 buddy->cal_working_hours->end_time =
549 atoi(tmp = sipe_xml_data(sipe_xml_child(xn_working_period, "EndTimeInMinutes")));
550 g_free(tmp);
553 std->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, std, dst);
554 dst->switch_time = sipe_cal_get_std_dst_time(now, buddy->cal_working_hours->bias, dst, std);
556 /* TST8TDT7,M3.2.0/02:00:00,M11.1.0/02:00:00 */
557 buddy->cal_working_hours->tz =
558 g_strdup_printf("TST%dTDT%d,M%d.%d.%d/%s,M%d.%d.%d/%s",
559 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60,
560 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60,
562 buddy->cal_working_hours->dst.month,
563 buddy->cal_working_hours->dst.day_order,
564 sipe_cal_get_wday(buddy->cal_working_hours->dst.day_of_week),
565 buddy->cal_working_hours->dst.time,
567 buddy->cal_working_hours->std.month,
568 buddy->cal_working_hours->std.day_order,
569 sipe_cal_get_wday(buddy->cal_working_hours->std.day_of_week),
570 buddy->cal_working_hours->std.time
572 /* TST8 */
573 buddy->cal_working_hours->tz_std =
574 g_strdup_printf("TST%d",
575 (buddy->cal_working_hours->bias + buddy->cal_working_hours->std.bias) / 60);
576 /* TDT7 */
577 buddy->cal_working_hours->tz_dst =
578 g_strdup_printf("TDT%d",
579 (buddy->cal_working_hours->bias + buddy->cal_working_hours->dst.bias) / 60);
582 struct sipe_cal_event*
583 sipe_cal_get_event(GSList *cal_events,
584 time_t time_in_question)
586 GSList *entry = cal_events;
587 struct sipe_cal_event* cal_event;
588 struct sipe_cal_event* res = NULL;
590 if (!cal_events || !IS(time_in_question)) return NULL;
592 while (entry) {
593 cal_event = entry->data;
594 /* event is in the past or in the future */
595 if (cal_event->start_time > time_in_question ||
596 cal_event->end_time <= time_in_question)
598 entry = entry->next;
599 continue;
602 if (!res) {
603 res = cal_event;
604 } else {
605 int res_status = (res->cal_status == SIPE_CAL_NO_DATA) ? -1 : res->cal_status;
606 int cal_status = (cal_event->cal_status == SIPE_CAL_NO_DATA) ? -1 : cal_event->cal_status;
607 if (res_status < cal_status) {
608 res = cal_event;
611 entry = entry->next;
613 return res;
616 static int
617 sipe_cal_get_status0(const gchar *free_busy,
618 time_t cal_start,
619 int granularity,
620 time_t time_in_question,
621 int *index)
623 int res = SIPE_CAL_NO_DATA;
624 int shift;
625 time_t cal_end = cal_start + strlen(free_busy)*granularity*60 - 1;
627 if (!(time_in_question >= cal_start && time_in_question <= cal_end)) return res;
629 shift = (time_in_question - cal_start) / (granularity*60);
630 if (index) {
631 *index = shift;
634 res = free_busy[shift] - '0';
636 return res;
640 * Returns time when current calendar state started
642 static time_t
643 sipe_cal_get_since_time(const gchar *free_busy,
644 time_t calStart,
645 int granularity,
646 int index,
647 int current_state)
649 int i;
651 if ((index < 0) || ((size_t)(index + 1) > strlen(free_busy))) return 0;
653 for (i = index; i >= 0; i--) {
654 int temp_status = free_busy[i] - '0';
656 if (current_state != temp_status) {
657 return calStart + (i + 1)*granularity*60;
660 if (i == 0) return calStart;
663 return 0;
665 static char*
666 sipe_cal_get_free_busy(struct sipe_buddy *buddy);
669 sipe_cal_get_status(struct sipe_buddy *buddy,
670 time_t time_in_question,
671 time_t *since)
673 time_t cal_start;
674 const char* free_busy;
675 int ret = SIPE_CAL_NO_DATA;
676 time_t state_since;
677 int index;
679 if (!buddy || !buddy->cal_start_time || !buddy->cal_granularity) {
680 SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data1 for %s, exiting",
681 buddy ? (buddy->name ? buddy->name : "") : "");
682 return SIPE_CAL_NO_DATA;
685 if (!(free_busy = sipe_cal_get_free_busy(buddy))) {
686 SIPE_DEBUG_INFO("sipe_cal_get_status: no calendar data2 for %s, exiting", buddy->name);
687 return SIPE_CAL_NO_DATA;
689 SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy);
691 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
693 ret = sipe_cal_get_status0(free_busy,
694 cal_start,
695 buddy->cal_granularity,
696 time_in_question,
697 &index);
698 state_since = sipe_cal_get_since_time(free_busy,
699 cal_start,
700 buddy->cal_granularity,
701 index,
702 ret);
704 if (since) *since = state_since;
705 return ret;
708 static time_t
709 sipe_cal_get_switch_time(const gchar *free_busy,
710 time_t calStart,
711 int granularity,
712 int index,
713 int current_state,
714 int *to_state)
716 size_t i;
717 time_t ret = TIME_NULL;
719 if ((index < 0) || ((size_t) (index + 1) > strlen(free_busy))) {
720 *to_state = SIPE_CAL_NO_DATA;
721 return ret;
724 for (i = index + 1; i < strlen(free_busy); i++) {
725 int temp_status = free_busy[i] - '0';
727 if (current_state != temp_status) {
728 *to_state = temp_status;
729 return calStart + i*granularity*60;
733 return ret;
736 static const char*
737 sipe_cal_get_tz(struct sipe_cal_working_hours *wh,
738 time_t time_in_question)
740 time_t dst_switch_time = (*wh).dst.switch_time;
741 time_t std_switch_time = (*wh).std.switch_time;
742 gboolean is_dst = FALSE;
744 /* No daylight savings */
745 if (dst_switch_time == TIME_NULL) {
746 return wh->tz_std;
749 if (dst_switch_time < std_switch_time) { /* North hemosphere - Europe, US */
750 if (time_in_question >= dst_switch_time && time_in_question < std_switch_time) {
751 is_dst = TRUE;
753 } else { /* South hemisphere - Australia */
754 if (time_in_question >= dst_switch_time || time_in_question < std_switch_time) {
755 is_dst = TRUE;
759 if (is_dst) {
760 return wh->tz_dst;
761 } else {
762 return wh->tz_std;
766 static time_t
767 sipe_cal_mktime_of_day(struct tm *sample_today_tm,
768 const int shift_minutes,
769 const char *tz)
771 sample_today_tm->tm_sec = 0;
772 sample_today_tm->tm_min = shift_minutes % 60;
773 sample_today_tm->tm_hour = shift_minutes / 60;
775 return sipe_mktime_tz(sample_today_tm, tz);
779 * Returns work day start and end in Epoch time
780 * considering the initial values are provided
781 * in contact's local time zone.
783 static void
784 sipe_cal_get_today_work_hours(struct sipe_cal_working_hours *wh,
785 time_t *start,
786 time_t *end,
787 time_t *next_start)
789 time_t now = time(NULL);
790 const char *tz = sipe_cal_get_tz(wh, now);
791 struct tm *remote_now_tm = sipe_localtime_tz(&now, tz);
793 if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_now_tm->tm_wday]))) {
794 /* not a work day */
795 *start = TIME_NULL;
796 *end = TIME_NULL;
797 *next_start = TIME_NULL;
798 return;
801 *end = sipe_cal_mktime_of_day(remote_now_tm, wh->end_time, tz);
803 if (now < *end) {
804 *start = sipe_cal_mktime_of_day(remote_now_tm, wh->start_time, tz);
805 *next_start = TIME_NULL;
806 } else { /* calculate start of tomorrow's work day if any */
807 time_t tom = now + 24*60*60;
808 struct tm *remote_tom_tm = sipe_localtime_tz(&tom, sipe_cal_get_tz(wh, tom));
810 if (!(wh->days_of_week && strstr(wh->days_of_week, wday_names[remote_tom_tm->tm_wday]))) {
811 /* not a work day */
812 *next_start = TIME_NULL;
815 *next_start = sipe_cal_mktime_of_day(remote_tom_tm, wh->start_time, sipe_cal_get_tz(wh, tom));
816 *start = TIME_NULL;
820 static int
821 sipe_cal_is_in_work_hours(const time_t time_in_question,
822 const time_t start,
823 const time_t end)
825 return !((time_in_question >= end) || (IS(start) && time_in_question < start));
829 * Returns time closest to now. Choses only from times ahead of now.
830 * Returns TIME_NULL otherwise.
832 static time_t
833 sipe_cal_get_until(const time_t now,
834 const time_t switch_time,
835 const time_t start,
836 const time_t end,
837 const time_t next_start)
839 time_t ret = TIME_NULL;
840 int min_diff = now - ret;
842 if (IS(switch_time) && switch_time > now && (switch_time - now) < min_diff) {
843 min_diff = switch_time - now;
844 ret = switch_time;
846 if (IS(start) && start > now && (start - now) < min_diff) {
847 min_diff = start - now;
848 ret = start;
850 if (IS(end) && end > now && (end - now) < min_diff) {
851 min_diff = end - now;
852 ret = end;
854 if (IS(next_start) && next_start > now && (next_start - now) < min_diff) {
855 min_diff = next_start - now;
856 ret = next_start;
858 return ret;
861 static char*
862 sipe_cal_get_free_busy(struct sipe_buddy *buddy)
864 /* do lazy decode if necessary */
865 if (!buddy->cal_free_busy && buddy->cal_free_busy_base64) {
866 gsize cal_dec64_len;
867 guchar *cal_dec64;
868 gsize i;
869 int j = 0;
871 cal_dec64 = g_base64_decode(buddy->cal_free_busy_base64, &cal_dec64_len);
873 buddy->cal_free_busy = g_malloc0(cal_dec64_len * 4 + 1);
875 http://msdn.microsoft.com/en-us/library/dd941537%28office.13%29.aspx
876 00, Free (Fr)
877 01, Tentative (Te)
878 10, Busy (Bu)
879 11, Out of facility (Oo)
881 http://msdn.microsoft.com/en-us/library/aa566048.aspx
882 0 Free
883 1 Tentative
884 2 Busy
885 3 Out of Office (OOF)
886 4 No data
888 for (i = 0; i < cal_dec64_len; i++) {
889 #define TWO_BIT_MASK 0x03
890 char tmp = cal_dec64[i];
891 buddy->cal_free_busy[j++] = (tmp & TWO_BIT_MASK) + '0';
892 buddy->cal_free_busy[j++] = ((tmp >> 2) & TWO_BIT_MASK) + '0';
893 buddy->cal_free_busy[j++] = ((tmp >> 4) & TWO_BIT_MASK) + '0';
894 buddy->cal_free_busy[j++] = ((tmp >> 6) & TWO_BIT_MASK) + '0';
896 buddy->cal_free_busy[j++] = '\0';
897 g_free(cal_dec64);
900 return buddy->cal_free_busy;
903 char *
904 sipe_cal_get_freebusy_base64(const char* freebusy_hex)
906 guint i = 0;
907 guint j = 0;
908 guint shift_factor = 0;
909 guint len, res_len;
910 guchar *res;
911 gchar *res_base64;
913 if (!freebusy_hex) return NULL;
915 len = strlen(freebusy_hex);
916 res_len = len / 4 + 1;
917 res = g_malloc0(res_len);
918 while (i < len) {
919 res[j] |= (freebusy_hex[i++] - '0') << shift_factor;
920 shift_factor += 2;
921 if (shift_factor == 8) {
922 shift_factor = 0;
923 j++;
927 res_base64 = g_base64_encode(res, shift_factor ? res_len : res_len - 1);
928 g_free(res);
929 return res_base64;
932 char *
933 sipe_cal_get_description(struct sipe_buddy *buddy)
935 time_t cal_start;
936 time_t cal_end;
937 int current_cal_state;
938 time_t now = time(NULL);
939 time_t start = TIME_NULL;
940 time_t end = TIME_NULL;
941 time_t next_start = TIME_NULL;
942 time_t switch_time;
943 int to_state = SIPE_CAL_NO_DATA;
944 time_t until = TIME_NULL;
945 int index = 0;
946 gboolean has_working_hours = (buddy->cal_working_hours != NULL);
947 const char *free_busy;
948 const char *cal_states[] = {_("Free"),
949 _("Tentative"),
950 _("Busy"),
951 _("Out of office"),
952 _("No data")};
954 if (buddy->cal_granularity != 15) {
955 SIPE_DEBUG_INFO("sipe_cal_get_description: granularity %d is unsupported, exiting.", buddy->cal_granularity);
956 return NULL;
959 /* to lazy load if needed */
960 free_busy = sipe_cal_get_free_busy(buddy);
961 SIPE_DEBUG_INFO("sipe_cal_get_description: buddy->cal_free_busy=\n%s", free_busy ? free_busy : "");
963 if (!buddy->cal_free_busy || !buddy->cal_granularity || !buddy->cal_start_time) {
964 SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: no calendar data, exiting");
965 return NULL;
968 cal_start = sipe_utils_str_to_time(buddy->cal_start_time);
969 cal_end = cal_start + 60 * (buddy->cal_granularity) * strlen(buddy->cal_free_busy);
971 current_cal_state = sipe_cal_get_status0(free_busy, cal_start, buddy->cal_granularity, time(NULL), &index);
972 if (current_cal_state == SIPE_CAL_NO_DATA) {
973 SIPE_DEBUG_INFO_NOFORMAT("sipe_cal_get_description: calendar is undefined for present moment, exiting.");
974 return NULL;
977 switch_time = sipe_cal_get_switch_time(free_busy, cal_start, buddy->cal_granularity, index, current_cal_state, &to_state);
979 SIPE_DEBUG_INFO_NOFORMAT("\n* Calendar *");
980 if (buddy->cal_working_hours) {
981 sipe_cal_get_today_work_hours(buddy->cal_working_hours, &start, &end, &next_start);
983 SIPE_DEBUG_INFO("Remote now timezone : %s", sipe_cal_get_tz(buddy->cal_working_hours, now));
984 SIPE_DEBUG_INFO("std.switch_time(GMT): %s",
985 IS((*buddy->cal_working_hours).std.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).std.switch_time))) : "");
986 SIPE_DEBUG_INFO("dst.switch_time(GMT): %s",
987 IS((*buddy->cal_working_hours).dst.switch_time) ? asctime(gmtime(&((*buddy->cal_working_hours).dst.switch_time))) : "");
988 SIPE_DEBUG_INFO("Remote now time : %s",
989 asctime(sipe_localtime_tz(&now, sipe_cal_get_tz(buddy->cal_working_hours, now))));
990 SIPE_DEBUG_INFO("Remote start time : %s",
991 IS(start) ? asctime(sipe_localtime_tz(&start, sipe_cal_get_tz(buddy->cal_working_hours, start))) : "");
992 SIPE_DEBUG_INFO("Remote end time : %s",
993 IS(end) ? asctime(sipe_localtime_tz(&end, sipe_cal_get_tz(buddy->cal_working_hours, end))) : "");
994 SIPE_DEBUG_INFO("Rem. next_start time: %s",
995 IS(next_start) ? asctime(sipe_localtime_tz(&next_start, sipe_cal_get_tz(buddy->cal_working_hours, next_start))) : "");
996 SIPE_DEBUG_INFO("Remote switch time : %s",
997 IS(switch_time) ? asctime(sipe_localtime_tz(&switch_time, sipe_cal_get_tz(buddy->cal_working_hours, switch_time))) : "");
998 } else {
999 SIPE_DEBUG_INFO("Local now time : %s",
1000 asctime(localtime(&now)));
1001 SIPE_DEBUG_INFO("Local switch time : %s",
1002 IS(switch_time) ? asctime(localtime(&switch_time)) : "");
1004 SIPE_DEBUG_INFO("Calendar End (GMT) : %s", asctime(gmtime(&cal_end)));
1005 SIPE_DEBUG_INFO("current cal state : %s", cal_states[current_cal_state]);
1006 SIPE_DEBUG_INFO("switch cal state : %s", cal_states[to_state] );
1008 /* Calendar: string calculations */
1011 ALGORITHM (don't delete)
1012 (c)2009,2010 pier11 <pier11@operamail.com>
1014 SOD = Start of Work Day
1015 EOD = End of Work Day
1016 NSOD = Start of tomorrow's Work Day
1017 SW = Calendar status switch time
1019 if current_cal_state == Free
1020 until = min_t of SOD, EOD, NSOD, SW (min_t(x) = min(x-now) where x>now only)
1021 else
1022 until = SW
1024 if (!until && (cal_period_end > now + 8H))
1025 until = cal_period_end
1027 if (!until)
1028 return "Currently %", current_cal_state
1030 if (until - now > 8H)
1031 if (current_cal_state == Free && (work_hours && !in work_hours(now)))
1032 return "Outside of working hours for next 8 hours"
1033 else
1034 return "%s for next 8 hours", current_cal_state
1036 if (current_cal_state == Free)
1037 if (work_hours && until !in work_hours(now))
1038 "Not working"
1039 else
1040 "%s", current_cal_state
1041 " until %.2d:%.2d", until
1042 else
1043 "Currently %", current_cal_state
1044 if (work_hours && until !in work_hours(until))
1045 ". Outside of working hours at at %.2d:%.2d", until
1046 else
1047 ". %s at %.2d:%.2d", to_state, until
1050 if (current_cal_state < 1) { /* Free */
1051 until = sipe_cal_get_until(now, switch_time, start, end, next_start);
1052 } else {
1053 until = switch_time;
1056 if (!IS(until) && (cal_end - now > 8*60*60))
1057 until = cal_end;
1059 if (!IS(until)) {
1060 return g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1063 if (until - now > 8*60*60) {
1064 /* Free & outside work hours */
1065 if (current_cal_state < 1 && has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1066 return g_strdup(_("Outside of working hours for next 8 hours"));
1067 } else {
1068 return g_strdup_printf(_("%s for next 8 hours"), cal_states[current_cal_state]);
1072 if (current_cal_state < 1) { /* Free */
1073 const char *tmp;
1074 struct tm *until_tm = localtime(&until);
1076 if (has_working_hours && !sipe_cal_is_in_work_hours(now, start, end)) {
1077 tmp = _("Not working");
1078 } else {
1079 tmp = cal_states[current_cal_state];
1081 return g_strdup_printf(_("%s until %.2d:%.2d"), tmp, until_tm->tm_hour, until_tm->tm_min);
1082 } else { /* Tentative or Busy or OOF */
1083 char *tmp;
1084 char *res;
1085 struct tm *until_tm = localtime(&until);
1087 tmp = g_strdup_printf(_("Currently %s"), cal_states[current_cal_state]);
1088 if (has_working_hours && !sipe_cal_is_in_work_hours(until, start, end)) {
1089 res = g_strdup_printf(_("%s. Outside of working hours at %.2d:%.2d"),
1090 tmp, until_tm->tm_hour, until_tm->tm_min);
1091 g_free(tmp);
1092 return res;
1093 } else {
1094 res = g_strdup_printf(_("%s. %s at %.2d:%.2d"), tmp, cal_states[to_state], until_tm->tm_hour, until_tm->tm_min);
1095 g_free(tmp);
1096 return res;
1099 /* End of - Calendar: string calculations */
1102 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
1104 void sipe_core_update_calendar(struct sipe_core_public *sipe_public)
1106 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
1108 /* Do in parallel.
1109 * If failed, the branch will be disabled for subsequent calls.
1110 * Can't rely that user turned the functionality on in account settings.
1112 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
1113 #ifdef _WIN32
1114 /* @TODO: UNIX integration missing */
1115 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
1116 #endif
1118 /* schedule repeat */
1119 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
1120 "<+update-calendar>",
1121 NULL,
1122 UPDATE_CALENDAR_INTERVAL,
1123 (sipe_schedule_action)sipe_core_update_calendar,
1124 NULL);
1126 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
1129 void sipe_cal_presence_publish(struct sipe_core_private *sipe_private,
1130 gboolean do_publish_calendar)
1132 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1133 if (do_publish_calendar)
1134 sipe_ocs2007_presence_publish(sipe_private, NULL);
1135 else
1136 sipe_ocs2007_category_publish(sipe_private);
1137 } else {
1138 sipe_ocs2005_presence_publish(sipe_private,
1139 do_publish_calendar);
1143 void sipe_cal_delayed_calendar_update(struct sipe_core_private *sipe_private)
1145 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
1147 sipe_schedule_seconds(sipe_private,
1148 "<+update-calendar>",
1149 NULL,
1150 UPDATE_CALENDAR_DELAY,
1151 (sipe_schedule_action) sipe_core_update_calendar,
1152 NULL);
1156 Local Variables:
1157 mode: c
1158 c-file-style: "bsd"
1159 indent-tabs-mode: t
1160 tab-width: 8
1161 End: