Include "config.h" instead of <config.h> Command used: find -name
[platform/upstream/glib.git] / gio / xdgmime / xdgmimeglob.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmimeglob.c: Private file.  Datastructure for storing the globs.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2003  Red Hat, Inc.
7  * Copyright (C) 2003  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include "xdgmimeglob.h"
33 #include "xdgmimeint.h"
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <assert.h>
37 #include <string.h>
38 #include <fnmatch.h>
39
40 #ifndef FALSE
41 #define FALSE   (0)
42 #endif
43
44 #ifndef TRUE
45 #define TRUE    (!FALSE)
46 #endif
47
48 typedef struct XdgGlobHashNode XdgGlobHashNode;
49 typedef struct XdgGlobList XdgGlobList;
50
51 struct XdgGlobHashNode
52 {
53   xdg_unichar_t character;
54   const char *mime_type;
55   int weight;
56   XdgGlobHashNode *next;
57   XdgGlobHashNode *child;
58 };
59 struct XdgGlobList
60 {
61   const char *data;
62   const char *mime_type;
63   int weight;
64   XdgGlobList *next;
65 };
66
67 struct XdgGlobHash
68 {
69   XdgGlobList *literal_list;
70   XdgGlobHashNode *simple_node;
71   XdgGlobList *full_list;
72 };
73
74
75 /* XdgGlobList
76  */
77 static XdgGlobList *
78 _xdg_glob_list_new (void)
79 {
80   XdgGlobList *new_element;
81
82   new_element = calloc (1, sizeof (XdgGlobList));
83
84   return new_element;
85 }
86
87 /* Frees glob_list and all of it's children */
88 static void
89 _xdg_glob_list_free (XdgGlobList *glob_list)
90 {
91   XdgGlobList *ptr, *next;
92
93   ptr = glob_list;
94
95   while (ptr != NULL)
96     {
97       next = ptr->next;
98
99       if (ptr->data)
100         free ((void *) ptr->data);
101       if (ptr->mime_type)
102         free ((void *) ptr->mime_type);
103       free (ptr);
104
105       ptr = next;
106     }
107 }
108
109 static XdgGlobList *
110 _xdg_glob_list_append (XdgGlobList *glob_list,
111                        void        *data,
112                        const char  *mime_type,
113                        int          weight)
114 {
115   XdgGlobList *new_element;
116   XdgGlobList *tmp_element;
117
118   new_element = _xdg_glob_list_new ();
119   new_element->data = data;
120   new_element->mime_type = mime_type;
121   new_element->weight = weight;
122   if (glob_list == NULL)
123     return new_element;
124
125   tmp_element = glob_list;
126   while (tmp_element->next != NULL)
127     tmp_element = tmp_element->next;
128
129   tmp_element->next = new_element;
130
131   return glob_list;
132 }
133
134 /* XdgGlobHashNode
135  */
136
137 static XdgGlobHashNode *
138 _xdg_glob_hash_node_new (void)
139 {
140   XdgGlobHashNode *glob_hash_node;
141
142   glob_hash_node = calloc (1, sizeof (XdgGlobHashNode));
143
144   return glob_hash_node;
145 }
146
147 static void
148 _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node,
149                           int depth)
150 {
151   int i;
152   for (i = 0; i < depth; i++)
153     printf (" ");
154
155   printf ("%c", (char)glob_hash_node->character);
156   if (glob_hash_node->mime_type)
157     printf (" - %s %d\n", glob_hash_node->mime_type, glob_hash_node->weight);
158   else
159     printf ("\n");
160   if (glob_hash_node->child)
161     _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1);
162   if (glob_hash_node->next)
163     _xdg_glob_hash_node_dump (glob_hash_node->next, depth);
164 }
165
166 static XdgGlobHashNode *
167 _xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node,
168                             xdg_unichar_t   *text,
169                             const char      *mime_type,
170                             int              weight)
171 {
172   XdgGlobHashNode *node;
173   xdg_unichar_t character;
174
175   character = text[0];
176
177   if ((glob_hash_node == NULL) ||
178       (character < glob_hash_node->character))
179     {
180       node = _xdg_glob_hash_node_new ();
181       node->character = character;
182       node->next = glob_hash_node;
183       glob_hash_node = node;
184     }
185   else if (character == glob_hash_node->character)
186     {
187       node = glob_hash_node;
188     }
189   else
190     {
191       XdgGlobHashNode *prev_node;
192       int found_node = FALSE;
193
194       /* Look for the first character of text in glob_hash_node, and insert it if we
195        * have to.*/
196       prev_node = glob_hash_node;
197       node = prev_node->next;
198
199       while (node != NULL)
200         {
201           if (character < node->character)
202             {
203               node = _xdg_glob_hash_node_new ();
204               node->character = character;
205               node->next = prev_node->next;
206               prev_node->next = node;
207
208               found_node = TRUE;
209               break;
210             }
211           else if (character == node->character)
212             {
213               found_node = TRUE;
214               break;
215             }
216           prev_node = node;
217           node = node->next;
218         }
219
220       if (! found_node)
221         {
222           node = _xdg_glob_hash_node_new ();
223           node->character = character;
224           node->next = prev_node->next;
225           prev_node->next = node;
226         }
227     }
228
229   text++;
230   if (*text == 0)
231     {
232       if (node->mime_type)
233         {
234           if (strcmp (node->mime_type, mime_type))
235             {
236               XdgGlobHashNode *child;
237               int found_node = FALSE;
238               
239               child = node->child;
240               while (child && child->character == 0)
241                 {
242                   if (strcmp (child->mime_type, mime_type) == 0)
243                     {
244                       found_node = TRUE;
245                       break;
246                     }
247                   child = child->next;
248                 }
249
250               if (!found_node)
251                 {
252                   child = _xdg_glob_hash_node_new ();
253                   child->character = 0;
254                   child->mime_type = strdup (mime_type);
255                   child->weight = weight;
256                   child->child = NULL;
257                   child->next = node->child;
258                   node->child = child;
259                 }
260             }
261         }
262       else
263         {
264           node->mime_type = strdup (mime_type);
265           node->weight = weight;
266         }
267     }
268   else
269     {
270       node->child = _xdg_glob_hash_insert_ucs4 (node->child, text, mime_type, weight);
271     }
272   return glob_hash_node;
273 }
274
275 /* glob must be valid UTF-8 */
276 static XdgGlobHashNode *
277 _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node,
278                             const char      *text,
279                             const char      *mime_type,
280                             int              weight)
281 {
282   XdgGlobHashNode *node;
283   xdg_unichar_t *unitext;
284   int len;
285
286   unitext = _xdg_convert_to_ucs4 (text, &len);
287   _xdg_reverse_ucs4 (unitext, len);
288   node = _xdg_glob_hash_insert_ucs4 (glob_hash_node, unitext, mime_type, weight);
289   free (unitext);
290   return node;
291 }
292
293 typedef struct {
294   const char *mime;
295   int weight;
296 } MimeWeight;
297
298 static int
299 _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node,
300                                       xdg_unichar_t   *file_name,
301                                       int              len,
302                                       int              ignore_case,
303                                       MimeWeight       mime_types[],
304                                       int              n_mime_types)
305 {
306   int n;
307   XdgGlobHashNode *node;
308   xdg_unichar_t character;
309
310   if (glob_hash_node == NULL)
311     return 0;
312
313   character = file_name[len - 1];
314   if (ignore_case)
315     character = _xdg_ucs4_to_lower(character);
316
317   for (node = glob_hash_node; node && character >= node->character; node = node->next)
318     {
319       if (character == node->character)
320         {
321           len--;
322           n = 0;
323           if (len > 0) 
324             {
325               n = _xdg_glob_hash_node_lookup_file_name (node->child,
326                                                         file_name,
327                                                         len,
328                                                         ignore_case,
329                                                         mime_types,
330                                                         n_mime_types);
331             }
332           if (n == 0)
333             {
334               if (node->mime_type)
335                 {
336                   mime_types[n].mime = node->mime_type;
337                   mime_types[n].weight = node->weight;
338                   n++; 
339                 }
340               node = node->child;
341               while (n < n_mime_types && node && node->character == 0)
342                 {
343                   if (node->mime_type)
344                     {
345                       mime_types[n].mime = node->mime_type;
346                       mime_types[n].weight = node->weight;
347                       n++;
348                     }
349                   node = node->next;
350                 }
351             }
352           return n;
353         }
354     }
355
356   return 0;
357 }
358
359 static int compare_mime_weight (const void *a, const void *b)
360 {
361   const MimeWeight *aa = (const MimeWeight *)a;
362   const MimeWeight *bb = (const MimeWeight *)b;
363
364   return aa->weight - bb->weight;
365 }
366
367 int
368 _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
369                                  const char  *file_name,
370                                  const char  *mime_types[],
371                                  int          n_mime_types)
372 {
373   XdgGlobList *list;
374   int i, n;
375   MimeWeight mimes[10];
376   int n_mimes = 10;
377   xdg_unichar_t *ucs4;
378   int len;
379
380   /* First, check the literals */
381
382   assert (file_name != NULL && n_mime_types > 0);
383
384   n = 0;
385
386   for (list = glob_hash->literal_list; list; list = list->next)
387     {
388       if (strcmp ((const char *)list->data, file_name) == 0)
389         {
390           mime_types[0] = list->mime_type;
391           return 1;
392         }
393     }
394
395   ucs4 = _xdg_convert_to_ucs4 (file_name, &len);
396   n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ucs4, len, FALSE,
397                                             mimes, n_mimes);
398   if (n == 0)
399     n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ucs4, len, TRUE,
400                                               mimes, n_mimes);
401   free(ucs4);
402
403   /* FIXME: Not UTF-8 safe */
404   if (n == 0)
405     {
406       for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next)
407         {
408           if (fnmatch ((const char *)list->data, file_name, 0) == 0)
409             {
410               mimes[n].mime = list->mime_type;
411               mimes[n].weight = list->weight;
412               n++;
413             }
414         }
415     }
416
417   qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight);
418
419   if (n_mime_types < n)
420     n = n_mime_types;
421
422   for (i = 0; i < n; i++)
423     mime_types[i] = mimes[i].mime;
424
425   return n;
426 }
427
428
429
430 /* XdgGlobHash
431  */
432
433 XdgGlobHash *
434 _xdg_glob_hash_new (void)
435 {
436   XdgGlobHash *glob_hash;
437
438   glob_hash = calloc (1, sizeof (XdgGlobHash));
439
440   return glob_hash;
441 }
442
443
444 static void
445 _xdg_glob_hash_free_nodes (XdgGlobHashNode *node)
446 {
447   if (node)
448     {
449       if (node->child)
450        _xdg_glob_hash_free_nodes (node->child);
451       if (node->next)
452        _xdg_glob_hash_free_nodes (node->next);
453       if (node->mime_type)
454         free ((void *) node->mime_type);
455       free (node);
456     }
457 }
458
459 void
460 _xdg_glob_hash_free (XdgGlobHash *glob_hash)
461 {
462   _xdg_glob_list_free (glob_hash->literal_list);
463   _xdg_glob_list_free (glob_hash->full_list);
464   _xdg_glob_hash_free_nodes (glob_hash->simple_node);
465   free (glob_hash);
466 }
467
468 XdgGlobType
469 _xdg_glob_determine_type (const char *glob)
470 {
471   const char *ptr;
472   int maybe_in_simple_glob = FALSE;
473   int first_char = TRUE;
474
475   ptr = glob;
476
477   while (*ptr != '\0')
478     {
479       if (*ptr == '*' && first_char)
480         maybe_in_simple_glob = TRUE;
481       else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
482           return XDG_GLOB_FULL;
483
484       first_char = FALSE;
485       ptr = _xdg_utf8_next_char (ptr);
486     }
487   if (maybe_in_simple_glob)
488     return XDG_GLOB_SIMPLE;
489   else
490     return XDG_GLOB_LITERAL;
491 }
492
493 /* glob must be valid UTF-8 */
494 void
495 _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
496                             const char  *glob,
497                             const char  *mime_type,
498                             int          weight)
499 {
500   XdgGlobType type;
501
502   assert (glob_hash != NULL);
503   assert (glob != NULL);
504
505   type = _xdg_glob_determine_type (glob);
506
507   switch (type)
508     {
509     case XDG_GLOB_LITERAL:
510       glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type), weight);
511       break;
512     case XDG_GLOB_SIMPLE:
513       glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type, weight);
514       break;
515     case XDG_GLOB_FULL:
516       glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type), weight);
517       break;
518     }
519 }
520
521 void
522 _xdg_glob_hash_dump (XdgGlobHash *glob_hash)
523 {
524   XdgGlobList *list;
525   printf ("LITERAL STRINGS\n");
526   if (!glob_hash || glob_hash->literal_list == NULL)
527     {
528       printf ("    None\n");
529     }
530   else
531     {
532       for (list = glob_hash->literal_list; list; list = list->next)
533         printf ("    %s - %s %d\n", (char *)list->data, list->mime_type, list->weight);
534     }
535   printf ("\nSIMPLE GLOBS\n");
536   if (!glob_hash || glob_hash->simple_node == NULL)
537     {
538       printf ("    None\n");
539     }
540   else
541     {
542       _xdg_glob_hash_node_dump (glob_hash->simple_node, 4);
543     }
544
545   printf ("\nFULL GLOBS\n");
546   if (!glob_hash || glob_hash->full_list == NULL)
547     {
548       printf ("    None\n");
549     }
550   else
551     {
552       for (list = glob_hash->full_list; list; list = list->next)
553         printf ("    %s - %s %d\n", (char *)list->data, list->mime_type, list->weight);
554     }
555 }
556
557
558 void
559 _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
560                                const char  *file_name)
561 {
562   FILE *glob_file;
563   char line[255];
564
565   glob_file = fopen (file_name, "r");
566
567   if (glob_file == NULL)
568     return;
569
570   /* FIXME: Not UTF-8 safe.  Doesn't work if lines are greater than 255 chars.
571    * Blah */
572   while (fgets (line, 255, glob_file) != NULL)
573     {
574       char *colon, *colon2;
575       char *mimetype, *glob;
576       int weight;
577
578       if (line[0] == '#')
579         continue;
580
581       colon = strchr (line, ':');
582       if (colon == NULL)
583         continue;
584       *(colon++) = '\0';
585       colon[strlen (colon) -1] = '\0';
586       colon2 = strchr (colon, ':');
587       if (colon2) 
588         {
589           *(colon2++) = '\000';
590           weight = atoi (line);
591           mimetype = colon;
592           glob = colon2;
593         }
594       else 
595         {
596           weight = 50;
597           mimetype = line;
598           glob = colon;
599         }
600       _xdg_glob_hash_append_glob (glob_hash, glob, mimetype, weight);
601     }
602
603   fclose (glob_file);
604 }