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