Imported Upstream version 0.8~alpha1
[platform/upstream/syncevolution.git] / src / e_cal_check_timezones.c
1 /*
2  * Copyright (C) 2008 Patrick Ohly
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
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, TITLE, NONINFRINGEMENT or FITNESS FOR A PARTICULAR
12  * PURPOSE.  See the GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17  * 02111-1307  USA
18  */
19
20 #include "e_cal_check_timezones.h"
21 #include <libecal/e-cal.h>
22 #include <string.h>
23
24 /**
25  * Matches a location to a system timezone definition via a fuzzy
26  * search and returns the matching TZID, or NULL if none found.
27  *
28  * Currently simply strips a suffix introduced by a hyphen,
29  * as in "America/Denver-(Standard)".
30  */
31 static const char *e_cal_match_location(const char *location)
32 {
33     icaltimezone *icomp;
34     const char *tail;
35     size_t len;
36     char *buffer;
37
38     icomp = icaltimezone_get_builtin_timezone (location);
39     if (icomp) {
40         return icaltimezone_get_tzid(icomp);
41     }
42
43     /* try a bit harder by stripping trailing suffix */
44     tail = strrchr(location, '-');
45     len = tail ? (tail - location) : strlen(location);
46     buffer = g_malloc(len + 1);
47
48     if (buffer) {
49         memcpy(buffer, location, len);
50         buffer[len] = 0;
51         icomp = icaltimezone_get_builtin_timezone (buffer);
52         g_free(buffer);
53         if (icomp) {
54             return icaltimezone_get_tzid(icomp);
55         }
56     }
57
58     return NULL;
59 }
60
61 /**
62  * matches a TZID against the system timezone definitions
63  * and returns the matching TZID, or NULL if none found
64  */
65 static const char *e_cal_match_tzid(const char *tzid)
66 {
67     const char *location;
68     const char *systzid;
69
70     /*
71      * old-style Evolution: /softwarestudio.org/Olson_20011030_5/America/Denver
72      *
73      * jump from one slash to the next and check whether the remainder
74      * is a known location; start with the whole string (just in case)
75      */
76     for (location = tzid;
77          location && location[0];
78          location = strchr(location + 1, '/')) {
79         systzid = e_cal_match_location(location[0] == '/' ?
80                                        location + 1 :
81                                        location);
82         if (systzid) {
83             return systzid;
84         }
85     }
86
87     /* TODO: lookup table for Exchange TZIDs */
88
89     return NULL;
90 }
91
92
93
94 gboolean e_cal_check_timezones(icalcomponent *comp,
95                                icaltimezone *(*tzlookup)(const char *tzid,
96                                                          const void *custom,
97                                                          GError **error),
98                                const void *custom,
99                                GError **error)
100 {
101     gboolean success = TRUE;
102     icalcomponent *subcomp = NULL;
103     icaltimezone *zone = icaltimezone_new();
104     char *key = NULL, *value = NULL;
105     char *buffer = NULL;
106     char *zonestr = NULL;
107     char *tzid = NULL;
108
109     /** a hash from old to new tzid; strings dynamically allocated */
110     GHashTable *mapping = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
111
112     *error = NULL;
113
114     if (!mapping || !zone) {
115         goto nomem;
116     }
117
118     /* iterate over all VTIMEZONE definitions */
119     subcomp = icalcomponent_get_first_component(comp,
120                                                 ICAL_VTIMEZONE_COMPONENT);
121     while (subcomp) {
122         if (icaltimezone_set_component(zone, subcomp)) {
123             g_free(tzid);
124             tzid = g_strdup(icaltimezone_get_tzid(zone));
125             if (tzid) {
126                 const char *newtzid = e_cal_match_tzid(tzid);
127                 if (newtzid) {
128                     /* matched against system time zone */
129                     g_free(key);
130                     key = g_strdup(tzid);
131                     if (!key) {
132                         goto nomem;
133                     }
134
135                     g_free(value);
136                     value = g_strdup(newtzid);
137                     if (!value) {
138                         goto nomem;
139                     }
140
141                     g_hash_table_insert(mapping, key, value);
142                     key =
143                         value = NULL;
144                 } else {
145                     zonestr = g_strdup(icalcomponent_as_ical_string(subcomp));
146
147                     /* check for collisions with existing timezones */
148                     int counter;
149                     for (counter = 0;
150                          counter < 100 /* sanity limit */;
151                          counter++) {
152                         icaltimezone *existing_zone;
153
154                         if (counter) {
155                             g_free(value);
156                             value = g_strdup_printf("%s %d", tzid, counter);
157                         }
158                         existing_zone = tzlookup(counter ? value : tzid,
159                                                  custom,
160                                                  error);
161                         if (!existing_zone) {
162                             if (*error) {
163                                 goto failed;
164                             } else {
165                                 break;
166                             }
167                         }
168                         g_free(buffer);
169                         buffer = g_strdup(icalcomponent_as_ical_string(icaltimezone_get_component(existing_zone)));
170
171                         if (counter) {
172                             char *fulltzid = g_strdup_printf("TZID:%s", value);
173                             size_t baselen = strlen("TZID:") + strlen(tzid);
174                             size_t fulllen = strlen(fulltzid);
175                             char *tzidprop;
176                             /*
177                              * Map TZID with counter suffix back to basename.
178                              */
179                             tzidprop = strstr(buffer, fulltzid);
180                             g_assert(tzidprop);
181                             if (tzidprop) {
182                                 g_assert(baselen < fulllen);
183                                 memmove(tzidprop + baselen,
184                                         tzidprop + fulllen,
185                                         strlen(tzidprop + fulllen) + 1);
186                             }
187                             g_free(fulltzid);
188                         }
189                             
190
191                         /*
192                          * If the strings are identical, then the
193                          * VTIMEZONE definitions are identical.  If
194                          * they are not identical, then VTIMEZONE
195                          * definitions might still be semantically
196                          * correct and we waste some space by
197                          * needlesly duplicating the VTIMEZONE. This
198                          * is expected to occur rarely (if at all) in
199                          * practice.
200                          */
201                         if (!strcmp(zonestr, buffer)) {
202                             break;
203                         }
204                     }
205
206                     if (!counter) {
207                         /* does not exist, nothing to do */
208                     } else {
209                         /* timezone renamed */
210                         icalproperty *prop = icalcomponent_get_first_property(subcomp,
211                                                                               ICAL_TZID_PROPERTY);
212                         while (prop) {
213                             icalproperty_set_value_from_string(prop, value, "NO");
214                             prop = icalcomponent_get_next_property(subcomp,
215                                                                    ICAL_ANY_PROPERTY);
216                         }
217                         g_free(key);
218                         key = g_strdup(tzid);
219                         g_hash_table_insert(mapping, key, value);
220                         key =
221                             value = NULL;
222                     }
223                 }
224             }
225         }
226
227         subcomp = icalcomponent_get_next_component(comp,
228                                                    ICAL_VTIMEZONE_COMPONENT);
229     }
230
231     /*
232      * now replace all TZID parameters in place
233      */
234     subcomp = icalcomponent_get_first_component(comp,
235                                                 ICAL_ANY_COMPONENT);
236     while (subcomp) {
237         /*
238          * Leave VTIMEZONE unchanged, iterate over properties of
239          * everything else.
240          *
241          * Note that no attempt is made to remove unused VTIMEZONE
242          * definitions. That would just make the code more complex for
243          * little additional gain.
244          */
245         if (icalcomponent_isa(subcomp) != ICAL_VTIMEZONE_COMPONENT) {
246             icalproperty *prop = icalcomponent_get_first_property(subcomp,
247                                                                   ICAL_ANY_PROPERTY);
248             while (prop) {
249                 icalparameter *param = icalproperty_get_first_parameter(prop,
250                                                                         ICAL_ANY_PARAMETER);
251                 while (param) {
252                     if (icalparameter_isa(param) == ICAL_TZID_PARAMETER) {
253                         const char *oldtzid;
254                         const char *newtzid;
255
256                         g_free(tzid);
257                         tzid = g_strdup(icalparameter_get_tzid(param));
258                         
259                         if (!g_hash_table_lookup_extended(mapping,
260                                                           tzid,
261                                                           (gpointer *)&oldtzid,
262                                                           (gpointer *)&newtzid)) {
263                             /* Corresponding VTIMEZONE not seen before! */
264                             newtzid = e_cal_match_tzid(tzid);
265                         }
266                         if (newtzid) {
267                             icalparameter_set_tzid(param, newtzid);
268                         }
269                     }
270                     param = icalproperty_get_next_parameter(prop,
271                                                             ICAL_ANY_PARAMETER);
272                 }
273                 prop = icalcomponent_get_next_property(subcomp,
274                                                        ICAL_ANY_PROPERTY);
275             }
276         }
277         subcomp = icalcomponent_get_next_component(comp,
278                                                    ICAL_ANY_COMPONENT);
279     }
280     
281     goto done;
282  nomem:
283     /* TODO: set gerror for "out of memory" */
284  failed:
285     /* gerror should have been set already */
286     g_assert(*error);
287     success = FALSE;
288  done:
289     if (mapping) {
290         g_hash_table_destroy(mapping);
291     }
292     if (zone) {
293         icaltimezone_free(zone, 1);
294     }
295     g_free(tzid);
296     g_free(zonestr);
297     g_free(buffer);
298     g_free(key);
299     g_free(value);
300     
301     return success;
302 }
303
304 icaltimezone *e_cal_tzlookup_ecal(const char *tzid,
305                                   const void *custom,
306                                   GError **error)
307 {
308     ECal *ecal = (ECal *)custom;
309     icaltimezone *zone = NULL;
310
311     if (e_cal_get_timezone(ecal, tzid, &zone, error)) {
312         g_assert(*error == NULL);
313         return zone;
314     } else {
315         g_assert(*error);
316         if ((*error)->domain == E_CALENDAR_ERROR &&
317             (*error)->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND) {
318             /*
319              * we had to trigger this error to check for the timezone existance,
320              * clear it and return NULL
321              */
322             g_clear_error(error);
323         }
324         return NULL;
325     }
326 }
327
328 icaltimezone *e_cal_tzlookup_icomp(const char *tzid,
329                                    const void *custom,
330                                    GError **error)
331 {
332     const icalcomponent *icomp = custom;
333     /* TODO */
334
335     return NULL;
336 }