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 unsigned char category_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, char *fspath, unsigned char visiblep ) {
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..
51 // find or create category
54 if ( ( c = pnd_box_find_by_key ( m_categories, catname ) ) ) {
55 // category was found..
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 );
66 c -> catflags = CFNORMAL;
68 c -> catflags = CFHIDDEN;
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;
80 c -> fspath = strdup ( fspath );
87 return ( 1 ); // create cat, but skip app
90 // alloc and populate appref
92 mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
97 bzero ( ar, sizeof(mm_appref_t) );
102 // plug it into category
103 // and sort it on insert!
105 ar -> next = c -> refs;
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;
115 if ( iter -> ref -> title_en ) {
116 if ( cat_sort_score ( c, ar, iter ) < 0 ) {
117 // new guy is smaller than the current guy!
121 // since new guy must have a name by here, we're bigger than any guy who does not have a name
130 // smaller than the current guy, so stitch in
135 ar -> next = c -> refs;
139 // we're the biggest, just append to last
144 ar -> next = c -> refs;
153 int cat_sort_score ( mm_category_t *cat, mm_appref_t *s1, mm_appref_t *s2 ) {
155 // are we in a directory browser, or looking at pnd-files?
156 if ( cat -> fspath ) {
159 return ( 0 ); // equal
161 } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
162 s2 -> ref -> object_type == pnd_object_type_directory )
164 // both are directories, be nice
165 return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
166 } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
167 s2 -> ref -> object_type != pnd_object_type_directory )
169 return ( -1 ); // dir on the left is earlier than file on the right
170 } else if ( s1 -> ref -> object_type != pnd_object_type_directory &&
171 s2 -> ref -> object_type == pnd_object_type_directory )
173 return ( 1 ); // dir on the right is earlier than file on the left
176 return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
181 return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
184 void category_dump ( void ) {
186 // WHY AREN'T I SORTING ON INSERT?
189 mm_category_t *iter = pnd_box_get_head ( m_categories );
190 unsigned int counter = 0;
194 pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", counter, iter -> catname, iter -> refcount );
195 mm_appref_t *ar = iter -> refs;
198 pnd_log ( PND_LOG_DEFAULT, " Appref %s\n", IFNULL(ar -> ref -> title_en,"No Name") );
202 iter = pnd_box_get_next ( iter );
209 void category_freeall ( void ) {
210 mm_category_t *c, *cnext;
211 mm_appref_t *iter, *next;
213 c = pnd_box_get_head ( m_categories );
216 cnext = pnd_box_get_next ( c );
229 if ( c -> catname ) {
230 free ( c -> catname );
235 free ( c -> fspath );
239 pnd_box_delete_node ( m_categories, c );
248 unsigned char category_map_setup ( void ) {
250 char *searchpath = pnd_box_get_head ( g_conf );
252 if ( ! searchpath ) {
256 // look through conf for useful keys
257 while ( searchpath ) {
258 char *k = pnd_box_get_key ( searchpath );
260 // does this key look like a category mapping key?
261 if ( strncasecmp ( k, "categories.@", 12 ) == 0 ) {
264 // iterate across 'words' in v, assigning catmaps to them
267 //pnd_log ( pndn_debug, "target(%s) from(%s)\n", k, buffer );
269 category_push ( k, NULL /* parent cat */, NULL, 0, NULL /* fspath */, 1 );
270 g_catmaps [ g_catmapcount ].target = pnd_box_find_by_key ( m_categories, k );
271 g_catmaps [ g_catmapcount ].from = strdup ( buffer );
277 } // if key looks like catmap
279 searchpath = pnd_box_get_next ( searchpath );
280 } // while each conf key
285 mm_category_t *category_map_query ( char *cat ) {
288 for ( i = 0; i < g_catmapcount; i++ ) {
289 if ( strcasecmp ( g_catmaps [ i ].from, cat ) == 0 ) {
290 return ( g_catmaps [ i ].target );
297 unsigned char category_meta_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, unsigned char visiblep ) {
299 char catnamebuffer [ 512 ] = "";
302 return ( 1 ); // fine, just nada
305 //fprintf ( stderr, "meta push: '%s'\n", catname );
307 // push bad categories into Other (if we're not targeting All right now)
308 if ( pnd_conf_get_as_int_d ( g_conf, "categories.good_cats_only", 1 ) ) {
311 if ( strncmp ( catname, "All ", 4 ) != 0 ) {
313 // if this is a parent cat..
314 if ( catname && ! parentcatname ) {
316 // if bad, shove it to Other
317 if ( ! freedesktop_check_cat ( catname ) ) {
318 parentcatname = NULL;
319 catname = BADCATNAME;
320 visiblep = cat_is_visible ( g_conf, catname );
323 } else if ( catname && parentcatname ) {
326 // if parent is bad, then we probably already pushed it over, so don't do it again.
327 // if its parent is okay, but subcat is bad, push it to other. (ie: lets avoid duplication in Other)
328 if ( ! freedesktop_check_cat ( parentcatname ) ) {
332 } else if ( ! freedesktop_check_cat ( catname ) ) {
333 parentcatname = NULL;
334 catname = BADCATNAME;
335 visiblep = cat_is_visible ( g_conf, catname );
338 } // parent or child cat?
344 // if invisible, and a parent category name is known, prepend it for ease of use
345 if ( ! visiblep && parentcatname ) {
346 snprintf ( catnamebuffer, 500, "%s.%s", parentcatname, catname );
347 catname = catnamebuffer;
350 // do we honour cat mapping at all?
351 if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
353 // is this guy mapped?
354 cat = category_map_query ( catname );
357 category_push ( cat -> catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
361 // not mapped.. but default?
362 if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
363 char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
365 category_push ( def, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
370 } // cat map is desired?
372 // not default, just do it
373 category_push ( catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
375 // if subcats as folders, then lets just make up a dummy app that pretends to be a folder,
376 // and stuff it into the parent cat
377 if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) ) {
379 // it is implicit that since we're talking parentcat, its already been created in a previous call
380 // therefore, we need to..
381 // i) find the parent cat
382 // ii) ensure it already has a faux-disco container
383 // iii) ensure that disco container doesn't already contain a disco-entry for this subcat
384 // iv) create the dummy app folder by pushing the disco into the apprefs as normal
385 // v) create a dummy '..' for going back up, in the child
387 mm_category_t *pcat = pnd_box_find_by_key ( m_categories, parentcatname );
389 if ( ! pcat -> disco ) {
390 pcat -> disco = pnd_box_new ( pcat -> catname );
393 // if this subcat is already in the faux-disco list, then its probably already
394 // handled so we needn't concern ourselves anymore. If not, then we can
395 // create it and push it into the parent as a new 'app'
396 pnd_disco_t *disco = pnd_box_find_by_key ( pcat -> disco, catname );
400 disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
403 // create the subcat faux-disco entry, and register into parent cat
405 sprintf ( uid, "%p", catname );
407 disco -> unique_id = strdup ( uid );
408 if ( strchr ( catname, '.' ) ) {
409 disco -> title_en = strdup ( strchr ( catname, '.' ) + 1 );
411 disco -> title_en = strdup ( catname );
413 disco -> object_flags = PND_DISCO_GENERATED;
414 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
415 disco -> object_path = strdup ( catname );
417 category_push ( parentcatname, NULL /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
419 // create .. faux-disco entry into child cat
420 disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
422 sprintf ( uid, "%p", uid );
424 disco -> unique_id = strdup ( uid );
425 disco -> title_en = strdup ( ".." );
426 disco -> object_flags = PND_DISCO_GENERATED;
427 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
429 category_push ( catname, parentcatname /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
431 } // making faux disco entries
433 } // disco already exist?
435 } // subcat as folder?
440 //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
445 unsigned char category_fs_restock ( mm_category_t *cat ) {
447 if ( ! cat -> fspath ) {
448 return ( 1 ); // not a filesystem browser tab
451 // clear any existing baggage
455 mm_appref_t *iter = cat -> refs, *next;
464 if ( cat -> disco ) {
465 pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
468 n = pnd_box_get_next ( p );
469 pnd_disco_destroy ( p );
472 pnd_box_delete ( cat -> disco );
475 // rescan the filesystem
478 //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
481 if ( ( d = opendir ( cat -> fspath ) ) ) {
482 struct dirent *de = readdir ( d );
487 cat -> disco = pnd_box_new ( cat -> catname );
492 char fullpath [ PATH_MAX ];
493 sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
494 int statret = stat ( fullpath, &buffy );
496 // if file is executable somehow or another
498 buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
501 // determine unique-id
502 sprintf ( uid, "%d", (int) de -> d_ino );
505 switch ( de -> d_type ) {
508 if ( strcmp ( de -> d_name, "." ) == 0 ) {
509 // ignore ".", but ".." is fine
511 disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
512 disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
517 disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
518 disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
523 // found a directory or executable?
525 // register with current category
526 disco -> unique_id = strdup ( uid );
527 disco -> title_en = strdup ( de -> d_name );
528 disco -> object_flags = PND_DISCO_GENERATED;
529 disco -> object_path = strdup ( cat -> fspath );
530 disco -> object_filename = strdup ( de -> d_name );
531 category_push ( cat -> catname, NULL /* parent cat */, disco, 0 /* no ovr */, NULL /* fspath already set */, 1 /* visible */ );
532 // if a override icon exists, cache it up
533 cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
534 pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
549 static int catname_cmp ( const void *p1, const void *p2 ) {
550 //mm_category_t *c1 = (mm_category_t*) p1;
551 //mm_category_t *c2 = (mm_category_t*) p2;
552 mm_category_t *c1 = *( (mm_category_t**) p1 );
553 mm_category_t *c2 = *( (mm_category_t**) p2 );
555 if ( ( isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
557 } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( isalnum ( c1 -> catname [ 1 ] ) ) ) {
559 } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
563 int i = strcasecmp ( c1 -> catname, c2 -> catname );
564 //printf ( "cat name compare %p %s to %p %s = %d\n", p1, c1 -> catname, p2, c2 -> catname, i );
569 void category_sort ( void ) {
570 // we probably don't want to sort tab categories, since the user may have specified an ordering
571 // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
574 qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
578 qsort ( g_categories, g_categorycount, sizeof(mm_category_t*), catname_cmp );
584 void category_publish ( unsigned int filter_mask, char *param ) {
585 unsigned char interested;
587 // clear published categories
588 memset ( g_categories, '\0', sizeof(mm_category_t*) * MAX_CATS );
591 // figure out the start
592 mm_category_t *iter = pnd_box_get_head ( m_categories );
594 // for each category we know...
599 // is this category desired?
600 if ( filter_mask == CFALL ) {
602 } else if ( filter_mask == CFBYNAME ) {
603 if ( strcasecmp ( iter -> catname, param ) == 0 ) {
606 } else if ( iter -> catflags == filter_mask ) {
611 // set us up the bomb; notice that we're just duplicating the pointers, not making
612 // any new data here; none of this should ever be free'd!
613 g_categories [ g_categorycount ] = iter;
618 iter = pnd_box_get_next ( iter );
624 for ( i = 0; i < g_categorycount; i++ ) {
625 printf ( "Unsorted cat %d %p: %s\n", i, &(g_categories [ i ]), g_categories [ i ] -> catname );
629 // sort published categories
635 unsigned int category_count ( unsigned int filter_mask ) {
636 mm_category_t *iter = pnd_box_get_head ( m_categories );
637 unsigned int count = 0;
639 // for each category we know...
642 // is this category desired?
643 if ( iter -> catflags == filter_mask ) {
648 iter = pnd_box_get_next ( iter );
654 int category_index ( char *catname ) {
657 for ( i = 0; i < g_categorycount; i++ ) {
659 if ( strcasecmp ( g_categories [ i ] -> catname, catname ) == 0 ) {