c4a0533f9f4d296d601e5a116bd4863c58e03516
[pandora-libraries.git] / minimenu / mmcat.c
1
2 #include <string.h>
3 #include <strings.h>
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <dirent.h>
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <ctype.h>
10
11 #include "pnd_conf.h"
12 #include "pnd_logger.h"
13 #include "pnd_pxml.h"
14 #include "pnd_container.h"
15 #include "pnd_discovery.h"
16 #include "../lib/pnd_pathiter.h"
17
18 #include "mmenu.h"
19 #include "mmcache.h"
20 #include "mmcat.h"
21 #include "freedesktop_cats.h"
22
23 // all categories known -- visible, hidden, subcats, whatever.
24 pnd_box_handle m_categories = NULL;
25 unsigned int m_categorycount = 0;
26
27 // all categories being published right now -- a subset copied from m_categories
28 mm_category_t *g_categories [ MAX_CATS ];
29 unsigned char g_categorycount;
30
31 // category mappings
32 mm_catmap_t g_catmaps [ MAX_CATS ];
33 unsigned char g_catmapcount = 0;
34
35 extern pnd_conf_handle g_conf;
36
37 void category_init ( void ) {
38   m_categories = pnd_box_new ( "Schrodinger's cat" );
39   return;
40 }
41
42 unsigned char category_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, char *fspath, unsigned char visiblep ) {
43   mm_category_t *c;
44
45   // check category list; if found, append app to the end of it.
46   // if not found, add it to the category list and plop the app in there.
47   // app's are just app-refs, which contain links to the disco-t list -- thus removal is only in one place, and
48   // an app can be in multiple categories if we like..
49   //
50
51   // find or create category
52   //
53
54   if ( ( c = pnd_box_find_by_key ( m_categories, catname ) ) ) {
55     // category was found..
56   } else {
57     // category wasn't found..
58     //pnd_log ( PND_LOG_DEFAULT, "New category '%s'\n", catname );
59     c = pnd_box_allocinsert ( m_categories, catname, sizeof(mm_category_t) );
60     c -> catname = strdup ( catname );
61     if ( parentcatname ) {
62       c -> parent_catname = strdup ( parentcatname );
63     }
64
65     if ( visiblep ) {
66       c -> catflags = CFNORMAL;
67     } else {
68       c -> catflags = CFHIDDEN;
69     }
70
71     // if user prefers subcats-as-folders, lets reflag this sucker
72     if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) ) {
73       //printf ( "subcat: %s parent: %s\n", catname, parentcatname ? parentcatname : "none" );
74       c -> catflags = CFSUBCAT;
75     }
76
77     c -> refs = NULL;
78
79     if ( fspath ) {
80       c -> fspath = strdup ( fspath );
81     }
82
83     m_categorycount++;
84   }
85
86   if ( ! app ) {
87     return ( 1 ); // create cat, but skip app
88   }
89
90   // alloc and populate appref
91   //
92   mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
93   if ( ! ar ) {
94     return ( 0 );
95   }
96
97   bzero ( ar, sizeof(mm_appref_t) );
98
99   ar -> ref = app;
100   ar -> ovrh = ovrh;
101
102   // plug it into category
103   //   and sort it on insert!
104 #if 0 // no sorting
105   ar -> next = c -> refs;
106   c -> refs = ar;
107 #else // with sorting
108   // if no refs at all, or new guy has no title, just stick it in at head
109   if ( c -> refs && ar -> ref -> title_en ) {
110     mm_appref_t *iter = c -> refs;
111     mm_appref_t *last = NULL;
112
113     while ( iter ) {
114
115       if ( iter -> ref -> title_en ) {
116         if ( cat_sort_score ( c, ar, iter ) < 0 ) {
117           // new guy is smaller than the current guy!
118           break;
119         }
120       } else {
121         // since new guy must have a name by here, we're bigger than any guy who does not have a name
122         // --> continue
123       }
124
125       last = iter;
126       iter = iter -> next;
127     }
128
129     if ( iter ) {
130       // smaller than the current guy, so stitch in
131       if ( last ) {
132         ar -> next = iter;
133         last -> next = ar;
134       } else {
135         ar -> next = c -> refs;
136         c -> refs = ar;
137       }
138     } else {
139       // we're the biggest, just append to last
140       last -> next = ar;
141     }
142
143   } else {
144     ar -> next = c -> refs;
145     c -> refs = ar;
146   }
147 #endif
148   c -> refcount++;
149
150   return ( 1 );
151 }
152
153 int cat_sort_score ( mm_category_t *cat, mm_appref_t *s1, mm_appref_t *s2 ) {
154
155   // are we in a directory browser, or looking at pnd-files?
156   if ( cat -> fspath ) {
157     // directory browser mode
158
159     if ( s1 == s2 ) {
160       return ( 0 ); // equal
161
162     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
163                 s2 -> ref -> object_type == pnd_object_type_directory )
164     {
165       // both are directories, be nice
166       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
167     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
168                 s2 -> ref -> object_type != pnd_object_type_directory )
169     {
170       return ( -1 ); // dir on the left is earlier than file on the right
171     } else if ( s1 -> ref -> object_type != pnd_object_type_directory &&
172                 s2 -> ref -> object_type == pnd_object_type_directory )
173     {
174       return ( 1 ); // dir on the right is earlier than file on the left
175     } else {
176       // file on file
177       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
178     }
179
180   }
181
182   // pnd tab
183   //
184
185   // if this is comparing subcat folder to subcat folder, or pnd to pnd, or pnd to subcat folder?
186   unsigned char s1sub = 0;
187   unsigned char s2sub = 0;
188   if ( s1 -> ref -> object_type == pnd_object_type_directory ) {
189     s1sub = 1;
190   }
191   if ( s2 -> ref -> object_type == pnd_object_type_directory ) {
192     s2sub = 1;
193   }
194
195   if        ( (   s1sub ) && (   s2sub ) ) {
196     return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
197   } else if ( (   s1sub ) && ( ! s2sub ) ) {
198     return ( -1 );
199   } else if ( ( ! s1sub ) && (   s2sub ) ) {
200     return ( 1 );
201   } else if ( ( ! s1sub ) && ( ! s2sub ) ) {
202     return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
203   }
204
205   return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
206 }
207
208 void category_dump ( void ) {
209
210   // WHY AREN'T I SORTING ON INSERT?
211
212   // dump
213   mm_category_t *iter = pnd_box_get_head ( m_categories );
214   unsigned int counter = 0;
215
216   while ( iter ) {
217
218     pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", counter, iter -> catname, iter -> refcount );
219     mm_appref_t *ar = iter -> refs;
220
221     while ( ar ) {
222       pnd_log ( PND_LOG_DEFAULT, "  Appref %s\n", IFNULL(ar -> ref -> title_en,"No Name") );
223       ar = ar -> next;
224     }
225
226     iter = pnd_box_get_next ( iter );
227     counter++;
228   }
229
230   return;
231 }
232
233 void category_freeall ( void ) {
234   mm_category_t *c, *cnext;
235   mm_appref_t *iter, *next;
236
237   c = pnd_box_get_head ( m_categories );
238
239   while ( c ) {
240     cnext = pnd_box_get_next ( c );
241
242     // wipe 'em
243     iter = c -> refs;
244
245     while ( iter ) {
246       next = iter -> next;
247       free ( iter );
248       iter = next;
249     }
250
251     c -> refs = NULL;
252
253     if ( c -> catname ) {
254       free ( c -> catname );
255       c -> catname = NULL;
256     }
257
258     if ( c -> fspath ) {
259       free ( c -> fspath );
260       c -> fspath = NULL;
261     }
262
263     pnd_box_delete_node ( m_categories, c );
264
265     // next
266     c = cnext;
267   }
268
269   return;
270 }
271
272 unsigned char category_map_setup ( void ) {
273
274   char *searchpath = pnd_box_get_head ( g_conf );
275
276   if ( ! searchpath ) {
277     return ( 0 );
278   }
279
280   // look through conf for useful keys
281   while ( searchpath ) {
282     char *k = pnd_box_get_key ( searchpath );
283
284     // does this key look like a category mapping key?
285     if ( strncasecmp ( k, "categories.@", 12 ) == 0 ) {
286       k += 12;
287
288       // iterate across 'words' in v, assigning catmaps to them
289       SEARCHCHUNK_PRE
290       {
291         //pnd_log ( pndn_debug, "target(%s) from(%s)\n", k, buffer );
292
293         category_push ( k, NULL /* parent cat */, NULL, 0, NULL /* fspath */, 1 );
294         g_catmaps [ g_catmapcount ].target = pnd_box_find_by_key ( m_categories, k );
295         g_catmaps [ g_catmapcount ].from = strdup ( buffer );
296         g_catmapcount++;
297
298       }
299       SEARCHCHUNK_POST
300
301     } // if key looks like catmap
302
303     searchpath = pnd_box_get_next ( searchpath );
304   } // while each conf key
305
306   return ( 1 );
307 }
308
309 mm_category_t *category_map_query ( char *cat ) {
310   unsigned char i;
311
312   for ( i = 0; i < g_catmapcount; i++ ) {
313     if ( strcasecmp ( g_catmaps [ i ].from, cat ) == 0 ) {
314       return ( g_catmaps [ i ].target );
315     }
316   }
317
318   return ( NULL );
319 }
320
321 unsigned char category_meta_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, unsigned char visiblep, unsigned char parentp ) {
322   mm_category_t *cat;
323 #if 0 // prepending
324   char catnamebuffer [ 512 ] = "";
325 #endif
326
327   //fprintf ( stderr, "meta push: '%s'\n", catname );
328
329   if ( ! catname ) {
330     return ( 1 ); // fine, just nada
331   }
332
333   // we don't screw with "All" category that mmenu.c generates on the fly
334   if ( strncmp ( catname, "All ", 4 ) == 0 ) {
335     goto category_done_audit;
336   }
337
338   // check if category came from an ovr-file; if so, we implicitly trust it instead of enforcing rules
339   if ( app -> object_flags & ( PND_DISCO_CUSTOM1 | PND_DISCO_CUSTOM1 ) ) {
340     goto category_done_audit;
341   }
342
343   // category cleansing; lets..
344   // - ensure we only let good freedesktop categories through
345   // - we fix case.. no more UtIliTy (a good cat, studlycaps)
346   // - no more good cats but swapped ancestry; Utility as child of something?
347   // - if bogus, we just ship it off to BAD_CAT
348
349   unsigned char cat_is_clean = 1;
350   freedesktop_cat_t *fdcat = NULL, *fdpcat = NULL;
351   fdcat = freedesktop_category_query ( catname );
352   if ( parentcatname ) {
353     fdpcat = freedesktop_category_query ( parentcatname );
354   }
355
356   // ensure requested cat is good
357   if ( ! fdcat ) {
358     // requested cat is bad, send it to Other
359     cat_is_clean = 0;
360     printf ( "PXML Fail %s: Cat request %s (parent %s) -> bad cat\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
361
362     // do the Other substitution right away, so remaining code has something to look at in fdcat
363     fdcat = freedesktop_category_query ( BADCATNAME );
364     catname = fdcat -> cat;
365     fdpcat = NULL;
366     parentcatname = NULL;
367
368   } else {
369     // use canonicle entry, so our Case is now correct!
370     catname = fdcat -> cat;
371   }
372
373   // ensure parent is good, if specified
374   if ( parentcatname ) {
375     if ( ! fdpcat ) {
376       // requested cat is bad, send it to Other
377       cat_is_clean = 0;
378       printf ( "PXML Fail %s: Cat request %s (parent %s) -> parent bad cat\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
379       // fix immediately so code doesn't explode
380       parentcatname = NULL;
381     } else {
382       // use canonicle entry, so our Case is now correct!
383       parentcatname = fdpcat -> cat;
384     }
385   }
386
387   // ensure ancestry is good
388   // - if cat request is for child, ensure its a child
389   // - if parent specified, ensure its a parent
390   // - if child specified, ensure its parent is the right parent(?!)
391   //
392   if ( parentcatname ) {
393     // implies catname request is for child, with parent parentcatname
394
395     if ( fdcat -> parent_cat == NULL ) {
396       // but wait, catname is actually a parent cat...
397       cat_is_clean = 0;
398       printf ( "PXML Fail %s: Cat request %s (parent %s) -> cat wants to be child, but FD says its a parent\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
399     }
400     if ( fdpcat -> parent_cat ) {
401       // but wait, parent cat is actually a subcat!
402       cat_is_clean = 0;
403       printf ( "PXML Fail %s: Cat request %s (parent %s) -> parent cat, FD says its a child\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
404     }
405
406   } else {
407     // implies request is for a parent cat - itself has no parent
408
409     if ( fdcat -> parent_cat ) {
410       // but wait, cat actually has a parent!
411       cat_is_clean = 0;
412       printf ( "PXML Fail %s: Cat request %s (parent %s) -> cat wants to be parent, FD says its a child\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
413     }
414
415   }
416
417   // ensure that if this is a child cat, its parent is the right parent
418   if ( parentcatname ) {
419     if ( ( ! fdcat -> parent_cat ) ||
420          ( ! fdpcat ) )
421     { 
422       // child cat points to a different parent than requested parent!
423       cat_is_clean = 0;
424       printf ( "PXML Fail %s: Cat request %s (parent %s) -> cat wants to be child of a cat which FD says is the wrong parent (1)\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
425     } else if ( strcasecmp ( fdcat -> parent_cat, fdpcat -> cat ) != 0 ) {
426       // child cat points to a different parent than requested parent!
427       cat_is_clean = 0;
428       printf ( "PXML Fail %s: Cat request %s (parent %s) -> cat wants to be child of a cat which FD says is the wrong parent (2)\n", app -> title_en ? app -> title_en : "no name?", catname, parentcatname ? parentcatname : "n/a" );
429     }
430   }
431
432   // did testing fail? if so, bump to Other!
433   //
434   if ( ! cat_is_clean ) {
435     // set Other visibility
436     visiblep = cat_is_visible ( g_conf, BADCATNAME );
437     // fix cat request
438     fdcat = freedesktop_category_query ( BADCATNAME );
439     catname = fdcat -> cat;
440     // nullify parent cat request (if any)
441     fdpcat = NULL;
442     parentcatname = NULL;
443   } else {
444     //printf ( "PXML Category Pass: Cat request %s (parent %s)\n", catname, parentcatname ? parentcatname : "n/a" );
445   }
446
447   // push bad categories into Other (if we're not targeting All right now)
448 #if 0
449   if ( 1 /*pnd_conf_get_as_int_d ( g_conf, "categories.good_cats_only", 1 )*/ ) {
450
451     // don't audit All
452     if ( strncmp ( catname, "All ", 4 ) != 0 ) {
453
454       // if this is a parent cat..
455       if ( catname && ! parentcatname ) {
456
457         // if bad, shove it to Other
458         if ( ! freedesktop_check_cat ( catname ) ) {
459           parentcatname = NULL;
460           catname = BADCATNAME;
461           visiblep = cat_is_visible ( g_conf, catname );
462         }
463
464       } else if ( catname && parentcatname ) {
465         // this is a subcat
466
467         // if parent is bad, then we probably already pushed it over, so don't do it again.
468         // if its parent is okay, but subcat is bad, push it to other. (ie: lets avoid duplication in Other)
469         if ( ! freedesktop_check_cat ( parentcatname ) ) {
470           // skip
471           return ( 1 );
472
473         } else if ( ! freedesktop_check_cat ( catname ) ) {
474           parentcatname = NULL;
475           catname = BADCATNAME;
476           visiblep = cat_is_visible ( g_conf, catname );
477         }
478
479       } // parent or child cat?
480
481     } // not All
482
483   } // good cats only?
484 #endif
485
486  category_done_audit:
487
488   // if invisible, and a parent category name is known, prepend it for ease of use
489 #if 0 // prepending
490   if ( ! visiblep && parentcatname ) {
491     snprintf ( catnamebuffer, 500, "%s.%s", parentcatname, catname );
492     catname = catnamebuffer;
493   }
494 #endif
495
496   // if this is a subcat push, and its requesting special 'no subcat', then just ditch it
497   if ( parentcatname && strcmp ( catname, "NoSubcategory" ) == 0 ) {
498     return ( 1 );
499   }
500
501   // do we honour cat mapping at all?
502   if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
503
504     // is this guy mapped?
505     cat = category_map_query ( catname );
506
507     if ( cat ) {
508       category_push ( cat -> catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
509       goto meta_done;
510     }
511
512     // not mapped.. but default?
513     if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
514       char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
515       if ( def ) {
516         category_push ( def, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
517         goto meta_done;
518       }
519     }
520
521   } // cat map is desired?
522
523   // is app already in the target cat? (ie: its being pushed twice due to cat mapping or Other'ing or something..)
524   if ( app ) {
525     if ( category_contains_app ( catname, app -> unique_id ) ) {
526       printf ( "App Fail: app (%s %s) is already in cat %s\n", app -> title_en ? app -> title_en : "no name?", app -> unique_id, catname );
527       return ( 1 ); // success, already there!
528     }
529   }
530
531   // are we not putting apps into parent cat, when subcat is present? if so..
532   // if the cat we're looking at now is the main (or main alt-cat), and we also have a subcat, then ditch it.
533   // so, is the cat we're looking at right now the apps main (or main alt) cat?
534   if ( parentp ) {
535     // and does this app have sub/altsub cats?
536     if ( app -> main_category1 || app -> main_category2 ||
537          app -> alt_category1 || app -> alt_category2 )
538     {
539       // and we're only desiring the subcat version of the app?
540       if ( pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_to_parent", 1 ) == 0 ) {
541         // create the parent category, since we need to be able to place a folder here maybe
542         category_push ( catname, parentcatname /* parent cat */, NULL /* app */, NULL /* ovrh */, NULL /* fspath */, visiblep );
543         // bail
544         return ( 1 );
545       } // subcat to parent?
546     } // app has subcat?
547   } // tab we're looking at now is the main tab?
548
549   // not default, just do it
550   category_push ( catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
551
552   // if subcats as folders, then lets just make up a dummy app that pretends to be a folder,
553   // and stuff it into the parent cat
554   if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) && cat_is_visible ( g_conf, catname ) ) {
555
556     // it is implicit that since we're talking parentcat, its already been created in a previous call
557     // therefore, we need to..
558     // i) find the parent cat
559     // ii) ensure it already has a faux-disco container
560     // iii) ensure that disco container doesn't already contain a disco-entry for this subcat
561     // iv) create the dummy app folder by pushing the disco into the apprefs as normal
562     // v) create a dummy '..' for going back up, in the child
563
564     mm_category_t *pcat = pnd_box_find_by_key ( m_categories, parentcatname );
565
566     if ( ! pcat -> disco ) {
567       pcat -> disco = pnd_box_new ( pcat -> catname );
568     }
569
570     // if this subcat is already in the faux-disco list, then its probably already
571     // handled so we needn't concern ourselves anymore. If not, then we can
572     // create it and push it into the parent as a new 'app'
573     pnd_disco_t *disco = pnd_box_find_by_key ( pcat -> disco, catname );
574
575     if ( ! disco ) {
576
577       disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
578       if ( disco ) {
579
580         // create the subcat faux-disco entry, and register into parent cat .. if its visible
581         char uid [ 30 ];
582         sprintf ( uid, "%p", catname );
583
584         disco -> unique_id = strdup ( uid );
585         if ( strchr ( catname, '.' ) ) {
586           disco -> title_en = strdup ( strchr ( catname, '.' ) + 1 );
587         } else {
588           disco -> title_en = strdup ( catname );
589         }
590         disco -> object_flags = PND_DISCO_GENERATED;
591         disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
592         disco -> object_path = strdup ( catname );
593
594         category_push ( parentcatname, NULL /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
595
596         // create .. faux-disco entry into child cat
597         disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
598
599         sprintf ( uid, "%p", uid );
600
601         disco -> unique_id = strdup ( uid );
602         disco -> title_en = strdup ( ".." );
603         disco -> object_flags = PND_DISCO_GENERATED;
604         disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
605
606         category_push ( catname, parentcatname /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
607
608       } // making faux disco entries
609
610     } // disco already exist?
611
612   } // subcat as folder?
613
614   // hack :(
615  meta_done:
616
617   //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
618
619   return ( 1 );
620 }
621
622 unsigned char category_fs_restock ( mm_category_t *cat ) {
623
624   if ( ! cat -> fspath ) {
625     return ( 1 ); // not a filesystem browser tab
626   }
627
628   // clear any existing baggage
629   //
630
631   // apprefs
632   mm_appref_t *iter = cat -> refs, *next;
633   while ( iter ) {
634     next = iter -> next;
635     free ( iter );
636     iter = next;
637   }
638   cat -> refs = NULL;
639
640   // discos
641   if ( cat -> disco ) {
642     pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
643     pnd_disco_t *n;
644     while ( p ) {
645       n = pnd_box_get_next ( p );
646       pnd_disco_destroy ( p );
647       p = n;
648     }
649     pnd_box_delete ( cat -> disco );
650   }
651
652   // rescan the filesystem
653   //
654
655   //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
656   DIR *d;
657
658   if ( ( d = opendir ( cat -> fspath ) ) ) {
659     struct dirent *de = readdir ( d );
660
661     pnd_disco_t *disco;
662     char uid [ 100 ];
663
664     cat -> disco = pnd_box_new ( cat -> catname );
665
666     while ( de ) {
667
668       struct stat buffy;
669       char fullpath [ PATH_MAX ];
670       sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
671       int statret = stat ( fullpath, &buffy );
672
673       // if file is executable somehow or another
674       if ( statret == 0 &&
675            buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
676          )
677       {
678         // determine unique-id
679         sprintf ( uid, "%d", (int) de -> d_ino );
680         disco = NULL;
681
682         switch ( de -> d_type ) {
683
684         case DT_DIR:
685           if ( strcmp ( de -> d_name, "." ) == 0 ) {
686             // ignore ".", but ".." is fine
687           } else if ( strcmp ( de -> d_name, ".." ) == 0 && strcmp ( cat -> fspath, "/" ) == 0 ) {
688             // ignore ".." only if we're at the true root
689           } else {
690             disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
691             disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
692           }
693           break;
694         case DT_UNKNOWN:
695         case DT_REG:
696           disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
697           disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
698           break;
699
700         } // switch
701
702         // found a directory or executable?
703         if ( disco ) {
704           // register with current category
705           disco -> unique_id = strdup ( uid );
706           disco -> title_en = strdup ( de -> d_name );
707           disco -> object_flags = PND_DISCO_GENERATED;
708           disco -> object_path = strdup ( cat -> fspath );
709           disco -> object_filename = strdup ( de -> d_name );
710           category_push ( cat -> catname, NULL /* parent cat */, disco, 0 /* no ovr */, NULL /* fspath already set */, 1 /* visible */ );
711           // if a override icon exists, cache it up
712           cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
713                        pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
714         }
715
716       } // stat
717
718       // next
719       de = readdir ( d );
720     }
721
722     closedir ( d );
723   }
724
725   return ( 1 );
726 }
727
728 static int catname_cmp ( const void *p1, const void *p2 ) {
729   //mm_category_t *c1 = (mm_category_t*) p1;
730   //mm_category_t *c2 = (mm_category_t*) p2;
731   mm_category_t *c1 = *( (mm_category_t**) p1 );
732   mm_category_t *c2 = *( (mm_category_t**) p2 );
733
734   if ( ( isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
735     return ( -1 );
736   } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( isalnum ( c1 -> catname [ 1 ] ) ) ) {
737     return ( 1 );
738   } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
739     return ( 0 );
740   }
741
742   int i = strcasecmp ( c1 -> catname, c2 -> catname );
743   //printf ( "cat name compare %p %s to %p %s = %d\n", p1, c1 -> catname, p2, c2 -> catname, i );
744
745   return ( i );
746 }
747
748 void category_sort ( void ) {
749   // we probably don't want to sort tab categories, since the user may have specified an ordering
750   // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
751
752 #if 0
753   qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
754 #endif
755
756 #if 1
757   qsort ( g_categories, g_categorycount, sizeof(mm_category_t*), catname_cmp );
758 #endif
759
760   return;
761 }
762
763 void category_publish ( unsigned int filter_mask, char *param ) {
764   unsigned char interested;
765
766   // clear published categories
767   memset ( g_categories, '\0', sizeof(mm_category_t*) * MAX_CATS );
768   g_categorycount = 0;
769
770   // figure out the start
771   mm_category_t *iter = pnd_box_get_head ( m_categories );
772
773   // for each category we know...
774   while ( iter ) {
775
776     interested = 0;
777
778     // is this category desired?
779     if ( filter_mask == CFALL ) {
780       interested = 1;
781     } else if ( filter_mask == CFBYNAME ) {
782       if ( strcasecmp ( iter -> catname, param ) == 0 ) {
783         interested = 1;
784       }
785     } else if ( iter -> catflags == filter_mask ) {
786       interested = 1;
787     } // if
788
789     // desired tab?
790     if ( interested ) {
791
792       // lets only publish tabs that actually have an app in them .. just in case we've
793       // pruned out all the apps (sent them to Other or are suppressing apps in parent cats or
794       // something) at some point.
795       if ( iter -> fspath || iter -> refs ) {
796
797         // set us up the bomb; notice that we're just duplicating the pointers, not making
798         // any new data here; none of this should ever be free'd!
799         g_categories [ g_categorycount ] = iter;
800         g_categorycount++;
801
802       } // has apps?
803
804     } // interested?
805
806     // next
807     iter = pnd_box_get_next ( iter );
808   }
809
810   // dump
811 #if 0
812   unsigned int i;
813   for ( i = 0; i < g_categorycount; i++ ) {
814     printf ( "Unsorted cat %d %p: %s\n", i, &(g_categories [ i ]), g_categories [ i ] -> catname );
815   }
816 #endif
817
818   // sort published categories
819   category_sort();
820
821   return;
822 }
823
824 unsigned int category_count ( unsigned int filter_mask ) {
825   mm_category_t *iter = pnd_box_get_head ( m_categories );
826   unsigned int count = 0;
827
828   // for each category we know...
829   while ( iter ) {
830
831     // is this category desired?
832     if ( iter -> catflags == filter_mask ) {
833       count++;
834     } // if
835
836     // next
837     iter = pnd_box_get_next ( iter );
838   }
839
840   return ( count );
841 }
842
843 int category_index ( char *catname ) {
844   unsigned char i;
845  
846   for ( i = 0; i < g_categorycount; i++ ) {
847  
848     if ( strcasecmp ( g_categories [ i ] -> catname, catname ) == 0 ) {
849       return ( i );
850     }
851  
852   }
853  
854   return ( -1 );
855 }
856
857 unsigned char category_contains_app ( char *catname, char *unique_id ) {
858
859   mm_category_t *c = pnd_box_find_by_key ( m_categories, catname );
860
861   if ( ! c ) {
862     return ( 0 ); // wtf?
863   }
864
865   if ( ! c -> refs ) {
866     return ( 0 ); // no apps at all
867   }
868
869   mm_appref_t *iter = c -> refs;
870
871   while ( iter ) {
872
873     if ( strcmp ( iter -> ref -> unique_id, unique_id ) == 0 ) {
874       return ( 1 );
875     }
876
877     iter = iter -> next;
878   }
879
880   return ( 0 );
881 }