1 /*
2 * icalmerge -- merge iCalendar files, keeping the newer of any duplicate events
3 *
4 * Author: Bert Bos <bert@w3.org>
5 * Created: 30 Sep 2002
6 */
7
8 #include "config.h"
9 #include <assert.h>
10 #include <unistd.h>
11 #include <stdio.h>
12 #include <errno.h>
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <stdarg.h>
17 #include <getopt.h>
18 #include <ctype.h>
19 #include <libical/icalcomponent.h>
20 #include <libical/icalparser.h>
21 #include <libical/icalset.h>
22 #include <libical/icalfileset.h>
23
24
25 #define PRODID "-//W3C//NONSGML icalmerge " VERSION "//EN"
26
27 #define ERR_OUT_OF_MEM 1 /* Program exit codes */
28 #define ERR_USAGE 2
29 #define ERR_DATE 3
30 #define ERR_PARSE 4
31 #define ERR_FILEIO 5
32 #define ERR_ICAL_ERR 6 /* Other error */
33 #define ERR_HASH 7 /* Hash failure */
34
35 #define USAGE "Usage: ical2html input [input...] output\n\
36 inputs and output are iCalendar files\n"
37
38 /* Long command line options */
39 static struct option options[] = {
40 {0, 0, 0, 0}
41 };
42
43 #define OPTIONS ""
44
45
46
47 /* fatal -- print error message and exit with errcode */
fatal(int errcode,const char * message,...)48 static void fatal(int errcode, const char *message,...)
49 {
50 va_list args;
51 va_start(args, message);
52 vfprintf(stderr, message, args);
53 va_end(args);
54 exit(errcode);
55 }
56
57
58 /* debug -- print message on stderr */
debug(const char * message,...)59 static void debug(const char *message,...)
60 {
61 #ifdef DEBUG
62 va_list args;
63 va_start(args, message);
64 vfprintf(stderr, message, args);
65 va_end(args);
66 #endif /* DEBUG */
67 }
68
69
70 #ifdef HAVE_SEARCH_H
71 # include <search.h>
72 #else
73 /*
74 * hsearch() on Mac OS X 10.1.2 appears to be broken: there is no
75 * search.h, there is a search() in the C library, but it doesn't work
76 * properly. We include some hash functions here, protected by
77 * HAVE_SEARCH_H. Hopefully when search.h appears in Mac OS X,
78 * hsearch() will be fixed at the same time...
79 */
80
81 typedef struct entry {char *key; void *data;} ENTRY;
82 typedef enum {FIND, ENTER} ACTION;
83
84 static ENTRY *htab;
85 static int *htab_index1, *htab_index2;
86 static unsigned int htab_size = 0;
87 static unsigned int htab_inited;
88
89
90 /* isprime -- test if n is a prime number */
isprime(unsigned int n)91 static int isprime(unsigned int n)
92 {
93 /* Simplistic algorithm, probably good enough for now */
94 unsigned int i;
95 assert(n % 2); /* n not even */
96 for (i = 3; i * i < n; i += 2) if (n % i == 0) return 0;
97 return 1;
98 }
99
100
101 /* hcreate -- create a hash table for at least nel entries */
hcreate(size_t nel)102 static int hcreate(size_t nel)
103 {
104 /* Change nel to next higher prime */
105 for (nel |= 1; !isprime(nel); nel += 2) ;
106
107 /* Allocate hash table and array to keep track of initialized entries */
108 if (! (htab = malloc(nel * sizeof(*htab)))) return 0;
109 if (! (htab_index1 = malloc(nel * sizeof(*htab_index1)))) return 0;
110 if (! (htab_index2 = malloc(nel * sizeof(*htab_index2)))) return 0;
111 htab_inited = 0;
112 htab_size = nel;
113
114 return 1;
115 }
116
117
118 #if 0
119 /* hdestroy -- deallocate hash table */
120 static void hdestroy(void)
121 {
122 assert(htab_size);
123 free(htab_index1);
124 free(htab_index2);
125 free(htab);
126 htab_size = 0;
127 }
128 #endif
129
130
131 /* hsearch -- search for and/or insert an entry in the hash table */
hsearch(ENTRY item,ACTION action)132 static ENTRY *hsearch(ENTRY item, ACTION action)
133 {
134 unsigned int hval, i;
135 char *p;
136
137 assert(htab_size); /* There must be a hash table */
138
139 /* Compute a hash value */
140 #if 1
141 /* This function suggested by Dan Bernstein */
142 for (hval = 5381, p = item.key; *p; p++) hval = (hval * 33) ^ *p;
143 #else
144 i = hval = strlen(item.key);
145 do {i--; hval = (hval << 1) + item.key[i];} while (i > 0);
146 #endif
147 hval %= htab_size;
148 /* if (action == ENTER) debug("%d\n", hval); */
149
150 /* Look for either an empty slot or an entry with the wanted key */
151 i = hval;
152 while (htab_index1[i] < htab_inited
153 && htab_index2[htab_index1[i]] == i
154 && strcmp(htab[i].key, item.key) != 0) {
155 i = (i + 1) % htab_size; /* "Open" hash method */
156 if (i == hval) return NULL; /* Made full round */
157 }
158 /* Now we either have an empty slot or an entry with the same key */
159 if (action == ENTER) {
160 htab[i].key = item.key; /* Put the item in this slot */
161 htab[i].data = item.data;
162 if (htab_index1[i] >= htab_inited || htab_index2[htab_index1[i]] != i) {
163 /* Item was not yet used, mark it as used */
164 htab_index1[i] = htab_inited;
165 htab_index2[htab_inited] = i;
166 htab_inited++;
167 }
168 return &htab[i];
169 } else if (htab_index1[i] < htab_inited && htab_index2[htab_index1[i]] == i)
170 return &htab[i]; /* action == FIND, found key */
171
172 return NULL; /* Found empty slot */
173 }
174
175 #endif /* HAVE_SEARCH_H */
176
177
178 /* merge -- add b to a, keeping only newer entries in case of duplicates */
merge(icalcomponent ** a,icalcomponent * b)179 static void merge(icalcomponent **a, icalcomponent *b)
180 {
181 static int first_time = 1;
182 icalcomponent *h, *next;
183 icalproperty *mod_a, *mod_b;
184 struct icaltimetype modif_a, modif_b;
185 icalproperty *uid;
186 ENTRY *e, e1;
187
188 /* Create the hash table */
189 if (first_time) {
190 if (! hcreate(10000)) fatal(ERR_HASH, "%s\n", strerror(errno));
191 first_time = 0;
192 }
193
194 /* Iterate over VEVENTs */
195 for (h = icalcomponent_get_first_component(b, ICAL_VEVENT_COMPONENT);
196 h; h = next) {
197
198 next = icalcomponent_get_next_component(b, ICAL_VEVENT_COMPONENT);
199
200 uid = icalcomponent_get_first_property(h, ICAL_UID_PROPERTY);
201 /*debug("%s", uid ? icalproperty_get_uid(uid) : "NO UID!?");*/
202 if (!uid) continue; /* Error in iCalendar file */
203 e1.key = strdup(icalproperty_get_uid(uid));
204 if (! (e = hsearch(e1, FIND))) {
205
206 /* New UID, add the VEVENT to the hash and to the set a */
207 icalcomponent_remove_component(b, h); /* Move from b... */
208 icalcomponent_add_component(*a, h); /* ... to a */
209 e1.data = h;
210 if (! (e = hsearch(e1, ENTER)))
211 fatal(ERR_OUT_OF_MEM, "No room in hash table\n");
212 /*debug(" (added %lx)\n", h);*/
213
214 } else {
215
216 /* Already an entry with this UID, compare modified dates */
217 /*debug(" (found %lx", e->data);*/
218 mod_a = icalcomponent_get_first_property((icalcomponent*)e->data,
219 ICAL_LASTMODIFIED_PROPERTY);
220 if (!mod_a) continue; /* Hmmm... */
221 modif_a = icalproperty_get_lastmodified(mod_a);
222
223 mod_b = icalcomponent_get_first_property(h, ICAL_LASTMODIFIED_PROPERTY);
224 if (!mod_b) continue; /* Hmmm... */
225 modif_b = icalproperty_get_lastmodified(mod_b);
226
227 if (icaltime_compare(modif_a, modif_b) == -1) {
228 /* a is older than b, so replace it */
229 icalcomponent_remove_component(*a, (icalcomponent*)e->data);
230 icalcomponent_remove_component(b, h); /* Move from b... */
231 icalcomponent_add_component(*a, h); /* ... to a */
232 e->data = h;
233 /*debug(" replaced)\n");*/
234 } else {
235 /*debug(" ignored)\n");*/
236 }
237 free(e1.key);
238 }
239 }
240 }
241
242
243 /* read_stream -- read size bytes into s from stream d */
read_stream(char * s,size_t size,void * d)244 static char* read_stream(char *s, size_t size, void *d)
245 {
246 return fgets(s, size, (FILE*)d);
247 }
248
249
250 /* main */
main(int argc,char * argv[])251 int main(int argc, char *argv[])
252 {
253 FILE* stream;
254 icalcomponent *comp;
255 icalparser *parser;
256 char c;
257 icalset *out;
258 icalcomponent *newset;
259
260 /* We handle errors ourselves */
261 icalerrno = 0;
262
263 /* Read commandline */
264 while ((c = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
265 switch (c) {
266 default: fatal(ERR_USAGE, USAGE);
267 }
268 }
269
270 /* Initialize a new, empty icalendar */
271 newset = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
272 icalproperty_new_version("2.0"),
273 icalproperty_new_prodid(PRODID),
274 0);
275
276 /* Loop over remaining file arguments, except the last */
277 if (optind >= argc - 1) fatal(ERR_USAGE, USAGE);
278 while (optind != argc - 1) {
279
280 /* Open the file */
281 stream = fopen(argv[optind], "r");
282 if (!stream) fatal(ERR_FILEIO, "%s: %s\n", argv[optind], strerror(errno));
283
284 /* Create a new parser object */
285 /* Tell the parser what input stream it should use */
286 /* Let the parser read the file and return all components */
287 parser = icalparser_new();
288 icalparser_set_gen_data(parser, stream);
289 if (! (comp = icalparser_parse(parser, read_stream)))
290 fatal(ERR_PARSE, "Parse error: %s\n", icalerror_strerror(icalerrno));
291 icalparser_free(parser);
292 if (fclose(stream) != 0)
293 fatal(ERR_FILEIO, "%s: %s\n", argv[optind], strerror(errno));
294
295 /* Add all found components to a hash table, removing duplicates */
296 merge(&newset, comp);
297
298 optind++;
299 }
300
301 /* Get output file name */
302 (void) unlink(argv[optind]); /* Remove output file if it already exists */
303 out = icalfileset_new(argv[optind]);
304 if (!out) fatal(ERR_FILEIO, "%s: %s\n", argv[optind], strerror(errno));
305
306 /* Put new icalendar in output file, flush to disk and close file */
307 icalfileset_add_component(out, newset);
308 icalfileset_free(out);
309
310 /* Clean up */
311 /* icalcomponent_free(newset); */
312
313 return 0;
314 }
315