12 #include "pnd_logger.h"
14 #include "pnd_container.h"
15 #include "pnd_discovery.h"
16 #include "../lib/pnd_pathiter.h"
21 #include "freedesktop_cats.h"
23 // all categories known -- visible, hidden, subcats, whatever.
24 pnd_box_handle m_categories = NULL;
25 unsigned int m_categorycount = 0;
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;
32 mm_catmap_t g_catmaps [ MAX_CATS ];
33 unsigned char g_catmapcount = 0;
35 extern pnd_conf_handle g_conf;
37 void category_init ( void ) {
38 m_categories = pnd_box_new ( "Schrodinger's cat" );
42 static char *_normalize ( char *catname, char *parentcatname ) {
43 static char buffer [ 101 ];
44 snprintf ( buffer, 100, "%s*%s", catname, parentcatname ? parentcatname : "NoParent" );
48 unsigned char category_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, char *fspath, unsigned char visiblep ) {
51 // check category list; if found, append app to the end of it.
52 // if not found, add it to the category list and plop the app in there.
53 // app's are just app-refs, which contain links to the disco-t list -- thus removal is only in one place, and
54 // an app can be in multiple categories if we like..
57 // find or create category
60 if ( ( c = pnd_box_find_by_key ( m_categories, _normalize ( catname, parentcatname ) ) ) ) {
61 // category was found..
63 // category wasn't found..
64 //pnd_log ( PND_LOG_DEFAULT, "New category '%s'\n", catname );
65 c = pnd_box_allocinsert ( m_categories, _normalize ( catname, parentcatname ), sizeof(mm_category_t) );
66 c -> catname = strdup ( catname );
67 if ( parentcatname ) {
68 c -> parent_catname = strdup ( parentcatname );
72 c -> catflags = CFNORMAL;
74 c -> catflags = CFHIDDEN;
77 // if user prefers subcats-as-folders, lets reflag this sucker
78 if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) ) {
79 //printf ( "subcat: %s parent: %s\n", catname, parentcatname ? parentcatname : "none" );
80 c -> catflags = CFSUBCAT;
86 c -> fspath = strdup ( fspath );
93 return ( 1 ); // create cat, but skip app
96 // alloc and populate appref
98 mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
103 bzero ( ar, sizeof(mm_appref_t) );
108 // plug it into category
109 // and sort it on insert!
111 ar -> next = c -> refs;
113 #else // with sorting
114 // if no refs at all, or new guy has no title, just stick it in at head
115 if ( c -> refs && ar -> ref -> title_en ) {
116 mm_appref_t *iter = c -> refs;
117 mm_appref_t *last = NULL;
121 if ( iter -> ref -> title_en ) {
122 if ( cat_sort_score ( c, ar, iter ) < 0 ) {
123 // new guy is smaller than the current guy!
127 // since new guy must have a name by here, we're bigger than any guy who does not have a name
136 // smaller than the current guy, so stitch in
141 ar -> next = c -> refs;
145 // we're the biggest, just append to last
150 ar -> next = c -> refs;
159 int cat_sort_score ( mm_category_t *cat, mm_appref_t *s1, mm_appref_t *s2 ) {
161 // are we in a directory browser, or looking at pnd-files?
162 if ( cat -> fspath ) {
163 // directory browser mode
166 return ( 0 ); // equal
168 } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
169 s2 -> ref -> object_type == pnd_object_type_directory )
171 // both are directories, be nice
172 return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
173 } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
174 s2 -> ref -> object_type != pnd_object_type_directory )
176 return ( -1 ); // dir on the left is earlier than file on the right
177 } else if ( s1 -> ref -> object_type != pnd_object_type_directory &&
178 s2 -> ref -> object_type == pnd_object_type_directory )
180 return ( 1 ); // dir on the right is earlier than file on the left
183 return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
191 // if this is comparing subcat folder to subcat folder, or pnd to pnd, or pnd to subcat folder?
192 unsigned char s1sub = 0;
193 unsigned char s2sub = 0;
194 if ( s1 -> ref -> object_type == pnd_object_type_directory ) {
197 if ( s2 -> ref -> object_type == pnd_object_type_directory ) {
201 if ( ( s1sub ) && ( s2sub ) ) {
202 return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
203 } else if ( ( s1sub ) && ( ! s2sub ) ) {
205 } else if ( ( ! s1sub ) && ( s2sub ) ) {
207 } else if ( ( ! s1sub ) && ( ! s2sub ) ) {
208 return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
211 return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
214 void category_dump ( void ) {
216 // WHY AREN'T I SORTING ON INSERT?
219 mm_category_t *iter = pnd_box_get_head ( m_categories );
220 unsigned int counter = 0;
224 pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", counter, iter -> catname, iter -> refcount );
225 mm_appref_t *ar = iter -> refs;
228 pnd_log ( PND_LOG_DEFAULT, " Appref %s\n", IFNULL(ar -> ref -> title_en,"No Name") );
232 iter = pnd_box_get_next ( iter );
239 void category_freeall ( void ) {
240 mm_category_t *c, *cnext;
241 mm_appref_t *iter, *next;
243 c = pnd_box_get_head ( m_categories );
246 cnext = pnd_box_get_next ( c );
259 if ( c -> catname ) {
260 free ( c -> catname );
265 free ( c -> fspath );
269 pnd_box_delete_node ( m_categories, c );
278 unsigned char category_map_setup ( void ) {
280 char *searchpath = pnd_box_get_head ( g_conf );
282 if ( ! searchpath ) {
286 // look through conf for useful keys
287 while ( searchpath ) {
288 char *k = pnd_box_get_key ( searchpath );
290 // does this key look like a category mapping key?
291 if ( strncasecmp ( k, "categories.@", 12 ) == 0 ) {
294 // iterate across 'words' in v, assigning catmaps to them
297 //pnd_log ( pndn_debug, "target(%s) from(%s)\n", k, buffer );
299 category_push ( k, NULL /* parent cat */, NULL, 0, NULL /* fspath */, 1 );
300 g_catmaps [ g_catmapcount ].target = pnd_box_find_by_key ( m_categories, _normalize ( k, NULL /* TBD: hack, not sure if this is right default value */ ) );
301 g_catmaps [ g_catmapcount ].from = strdup ( buffer );
307 } // if key looks like catmap
309 searchpath = pnd_box_get_next ( searchpath );
310 } // while each conf key
315 mm_category_t *category_map_query ( char *cat ) {
318 for ( i = 0; i < g_catmapcount; i++ ) {
319 if ( strcasecmp ( g_catmaps [ i ].from, cat ) == 0 ) {
320 return ( g_catmaps [ i ].target );
327 unsigned char category_meta_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, unsigned char visiblep, unsigned char parentp ) {
330 char catnamebuffer [ 512 ] = "";
333 //fprintf ( stderr, "meta push: '%s'\n", catname );
336 return ( 1 ); // fine, just nada
339 // we don't screw with "All" category that mmenu.c generates on the fly
340 if ( strncmp ( catname, "All ", 4 ) == 0 ) {
341 goto category_done_audit;
344 // check if category came from an ovr-file; if so, we implicitly trust it instead of enforcing rules
345 if ( app -> object_flags & ( PND_DISCO_CUSTOM1 | PND_DISCO_CUSTOM2 ) ) {
346 goto category_done_audit;
349 // category cleansing; lets..
350 // - ensure we only let good freedesktop categories through
351 // - we fix case.. no more UtIliTy (a good cat, studlycaps)
352 // - no more good cats but swapped ancestry; Utility as child of something?
353 // - if bogus, we just ship it off to BAD_CAT
355 unsigned char cat_is_clean = 1;
356 freedesktop_cat_t *fdcat = NULL, *fdpcat = NULL;
357 fdcat = freedesktop_category_query ( catname, parentcatname );
358 if ( parentcatname ) {
359 fdpcat = freedesktop_category_query ( parentcatname, NULL );
362 // ensure requested cat is good
364 // requested cat is bad, send it to Other
366 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" );
368 // do the Other substitution right away, so remaining code has something to look at in fdcat
369 fdcat = freedesktop_category_query ( BADCATNAME, NULL );
370 catname = fdcat -> cat;
372 parentcatname = NULL;
375 // use canonicle entry, so our Case is now correct!
376 catname = fdcat -> cat;
379 // ensure parent is good, if specified
380 if ( parentcatname ) {
382 // requested cat is bad, send it to Other
384 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" );
385 // fix immediately so code doesn't explode
386 parentcatname = NULL;
388 // use canonicle entry, so our Case is now correct!
389 parentcatname = fdpcat -> cat;
393 // ensure ancestry is good
394 // - if cat request is for child, ensure its a child
395 // - if parent specified, ensure its a parent
396 // - if child specified, ensure its parent is the right parent(?!)
398 if ( parentcatname ) {
399 // implies catname request is for child, with parent parentcatname
401 if ( fdcat -> parent_cat == NULL ) {
402 // but wait, catname is actually a parent cat...
404 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" );
406 if ( fdpcat -> parent_cat ) {
407 // but wait, parent cat is actually a subcat!
409 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" );
413 // implies request is for a parent cat - itself has no parent
415 if ( fdcat -> parent_cat ) {
416 // but wait, cat actually has a parent!
418 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" );
423 // ensure that if this is a child cat, its parent is the right parent
424 if ( parentcatname ) {
425 if ( ( ! fdcat -> parent_cat ) ||
428 // child cat points to a different parent than requested parent!
430 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" );
431 } else if ( strcasecmp ( fdcat -> parent_cat, fdpcat -> cat ) != 0 ) {
432 // child cat points to a different parent than requested parent!
434 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" );
438 // did testing fail? if so, bump to Other!
440 if ( ! cat_is_clean ) {
441 // set Other visibility
442 visiblep = cat_is_visible ( g_conf, BADCATNAME );
444 fdcat = freedesktop_category_query ( BADCATNAME, NULL );
445 catname = fdcat -> cat;
446 // nullify parent cat request (if any)
448 parentcatname = NULL;
450 //printf ( "PXML Category Pass: Cat request %s (parent %s)\n", catname, parentcatname ? parentcatname : "n/a" );
453 // push bad categories into Other (if we're not targeting All right now)
455 if ( 1 /*pnd_conf_get_as_int_d ( g_conf, "categories.good_cats_only", 1 )*/ ) {
458 if ( strncmp ( catname, "All ", 4 ) != 0 ) {
460 // if this is a parent cat..
461 if ( catname && ! parentcatname ) {
463 // if bad, shove it to Other
464 if ( ! freedesktop_check_cat ( catname ) ) {
465 parentcatname = NULL;
466 catname = BADCATNAME;
467 visiblep = cat_is_visible ( g_conf, catname );
470 } else if ( catname && parentcatname ) {
473 // if parent is bad, then we probably already pushed it over, so don't do it again.
474 // if its parent is okay, but subcat is bad, push it to other. (ie: lets avoid duplication in Other)
475 if ( ! freedesktop_check_cat ( parentcatname ) ) {
479 } else if ( ! freedesktop_check_cat ( catname ) ) {
480 parentcatname = NULL;
481 catname = BADCATNAME;
482 visiblep = cat_is_visible ( g_conf, catname );
485 } // parent or child cat?
494 // if invisible, and a parent category name is known, prepend it for ease of use
496 if ( ! visiblep && parentcatname ) {
497 snprintf ( catnamebuffer, 500, "%s.%s", parentcatname, catname );
498 catname = catnamebuffer;
502 // if this is a subcat push, and its requesting special 'no subcat', then just ditch it
503 if ( parentcatname && strcmp ( catname, "NoSubcategory" ) == 0 ) {
507 // do we honour cat mapping at all?
508 if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
510 // is this guy mapped?
511 cat = category_map_query ( catname );
514 category_push ( cat -> catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
518 // not mapped.. but default?
519 if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
520 char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
522 category_push ( def, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
527 } // cat map is desired?
529 // is app already in the target cat? (ie: its being pushed twice due to cat mapping or Other'ing or something..)
531 if ( category_contains_app ( catname, parentcatname, app -> unique_id ) ) {
532 printf ( "App Fail: app (%s %s) is already in cat %s\n", app -> title_en ? app -> title_en : "no name?", app -> unique_id, catname );
533 return ( 1 ); // success, already there!
537 // are we not putting apps into parent cat, when subcat is present? if so..
538 // if the cat we're looking at now is the main (or main alt-cat), and we also have a subcat, then ditch it.
539 // so, is the cat we're looking at right now the apps main (or main alt) cat?
541 // and does this app have sub/altsub cats?
542 if ( app -> main_category1 || app -> main_category2 || app -> alt_category1 || app -> alt_category2 ) {
543 // and we're only desiring the subcat version of the app?
544 if ( pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_to_parent", 1 ) == 0 ) {
545 // create the parent category, since we need to be able to place a folder here maybe
546 category_push ( catname, parentcatname /* parent cat */, NULL /* app */, NULL /* ovrh */, NULL /* fspath */, visiblep );
549 } // subcat to parent?
551 } // tab we're looking at now is the main tab?
553 // not default, just do it
554 category_push ( catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
556 // if subcats as folders, then lets just make up a dummy app that pretends to be a folder,
557 // and stuff it into the parent cat
558 if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) && cat_is_visible ( g_conf, catname ) ) {
560 // it is implicit that since we're talking parentcat, its already been created in a previous call
561 // therefore, we need to..
562 // i) find the parent cat
563 // ii) ensure it already has a faux-disco container
564 // iii) ensure that disco container doesn't already contain a disco-entry for this subcat
565 // iv) create the dummy app folder by pushing the disco into the apprefs as normal
566 // v) create a dummy '..' for going back up, in the child
568 mm_category_t *pcat = pnd_box_find_by_key ( m_categories, _normalize ( parentcatname, NULL ) );
570 if ( ! pcat -> disco ) {
571 pcat -> disco = pnd_box_new ( pcat -> catname );
574 // if this subcat is already in the faux-disco list, then its probably already
575 // handled so we needn't concern ourselves anymore. If not, then we can
576 // create it and push it into the parent as a new 'app'
577 pnd_disco_t *disco = pnd_box_find_by_key ( pcat -> disco, catname );
581 disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
584 // create the subcat faux-disco entry, and register into parent cat .. if its visible
586 sprintf ( uid, "%p", catname );
588 disco -> unique_id = strdup ( uid );
589 if ( strchr ( catname, '.' ) ) {
590 disco -> title_en = strdup ( strchr ( catname, '.' ) + 1 );
592 disco -> title_en = strdup ( catname );
594 disco -> object_flags = PND_DISCO_GENERATED;
595 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
596 disco -> object_path = strdup ( _normalize ( catname, parentcatname ) );
598 category_push ( parentcatname, NULL /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
600 // create .. faux-disco entry into child cat
601 disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
603 sprintf ( uid, "%p", uid );
605 disco -> unique_id = strdup ( uid );
606 disco -> title_en = strdup ( ".." );
607 disco -> object_flags = PND_DISCO_GENERATED;
608 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
610 category_push ( catname, parentcatname /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
612 } // making faux disco entries
614 } // disco already exist?
616 } // subcat as folder?
621 //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
626 unsigned char category_fs_restock ( mm_category_t *cat ) {
628 if ( ! cat -> fspath ) {
629 return ( 1 ); // not a filesystem browser tab
632 // clear any existing baggage
636 mm_appref_t *iter = cat -> refs, *next;
645 if ( cat -> disco ) {
646 pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
649 n = pnd_box_get_next ( p );
650 pnd_disco_destroy ( p );
653 pnd_box_delete ( cat -> disco );
656 // rescan the filesystem
659 //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
662 if ( ( d = opendir ( cat -> fspath ) ) ) {
663 struct dirent *de = readdir ( d );
668 cat -> disco = pnd_box_new ( cat -> catname );
673 char fullpath [ PATH_MAX ];
674 sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
675 int statret = stat ( fullpath, &buffy );
677 // if file is executable somehow or another
679 buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
682 // determine unique-id
683 sprintf ( uid, "%d", (int) de -> d_ino );
686 switch ( de -> d_type ) {
689 if ( strcmp ( de -> d_name, "." ) == 0 ) {
690 // ignore ".", but ".." is fine
691 } else if ( strcmp ( de -> d_name, ".." ) == 0 && strcmp ( cat -> fspath, "/" ) == 0 ) {
692 // ignore ".." only if we're at the true root
694 disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
695 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
700 disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
701 disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
706 // found a directory or executable?
708 // register with current category
709 disco -> unique_id = strdup ( uid );
710 disco -> title_en = strdup ( de -> d_name );
711 disco -> object_flags = PND_DISCO_GENERATED;
712 disco -> object_path = strdup ( cat -> fspath );
713 disco -> object_filename = strdup ( de -> d_name );
714 category_push ( cat -> catname, NULL /* parent cat */, disco, 0 /* no ovr */, NULL /* fspath already set */, 1 /* visible */ );
715 // if a override icon exists, cache it up
716 cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
717 pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
732 static int catname_cmp ( const void *p1, const void *p2 ) {
733 //mm_category_t *c1 = (mm_category_t*) p1;
734 //mm_category_t *c2 = (mm_category_t*) p2;
735 mm_category_t *c1 = *( (mm_category_t**) p1 );
736 mm_category_t *c2 = *( (mm_category_t**) p2 );
738 if ( ( isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
740 } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( isalnum ( c1 -> catname [ 1 ] ) ) ) {
742 } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
746 int i = strcasecmp ( c1 -> catname, c2 -> catname );
747 //printf ( "cat name compare %p %s to %p %s = %d\n", p1, c1 -> catname, p2, c2 -> catname, i );
752 void category_sort ( void ) {
753 // we probably don't want to sort tab categories, since the user may have specified an ordering
754 // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
757 qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
761 qsort ( g_categories, g_categorycount, sizeof(mm_category_t*), catname_cmp );
767 void category_publish ( unsigned int filter_mask, char *param ) {
768 unsigned char interested;
770 // clear published categories
771 memset ( g_categories, '\0', sizeof(mm_category_t*) * MAX_CATS );
774 // figure out the start
775 mm_category_t *iter = pnd_box_get_head ( m_categories );
777 // for each category we know...
782 // is this category desired?
783 if ( filter_mask == CFALL ) {
785 } else if ( filter_mask == CFBYNAME ) {
786 char *foo = strchr ( param, '*' ) + 1;
787 if ( strncasecmp ( iter -> catname, param, strlen ( iter -> catname ) ) == 0 &&
788 strcasecmp ( iter -> parent_catname, foo ) == 0 )
792 } else if ( iter -> catflags == filter_mask ) {
799 // lets only publish tabs that actually have an app in them .. just in case we've
800 // pruned out all the apps (sent them to Other or are suppressing apps in parent cats or
801 // something) at some point.
802 if ( iter -> fspath || iter -> refs ) {
804 // set us up the bomb; notice that we're just duplicating the pointers, not making
805 // any new data here; none of this should ever be free'd!
806 g_categories [ g_categorycount ] = iter;
814 iter = pnd_box_get_next ( iter );
820 for ( i = 0; i < g_categorycount; i++ ) {
821 printf ( "Unsorted cat %d %p: %s\n", i, &(g_categories [ i ]), g_categories [ i ] -> catname );
825 // sort published categories
831 unsigned int category_count ( unsigned int filter_mask ) {
832 mm_category_t *iter = pnd_box_get_head ( m_categories );
833 unsigned int count = 0;
835 // for each category we know...
838 // is this category desired?
839 if ( iter -> catflags == filter_mask ) {
844 iter = pnd_box_get_next ( iter );
850 int category_index ( char *catname ) {
853 for ( i = 0; i < g_categorycount; i++ ) {
855 if ( strcasecmp ( g_categories [ i ] -> catname, catname ) == 0 ) {
864 unsigned char category_contains_app ( char *catname, char *parentcatname, char *unique_id ) {
866 mm_category_t *c = pnd_box_find_by_key ( m_categories, _normalize ( catname, parentcatname ) );
869 return ( 0 ); // wtf?
873 return ( 0 ); // no apps at all
876 mm_appref_t *iter = c -> refs;
880 if ( strcmp ( iter -> ref -> unique_id, unique_id ) == 0 ) {