+ category_push ( catname, parentcatname /* parent cat */, app, ovrh, NULL /* fspath */, visiblep );
+
+ // if subcats as folders, then lets just make up a dummy app that pretends to be a folder,
+ // and stuff it into the parent cat
+ if ( parentcatname && pnd_conf_get_as_int_d ( g_conf, "tabs.subcat_as_folders", 1 ) && cat_is_visible ( g_conf, catname ) ) {
+
+ // it is implicit that since we're talking parentcat, its already been created in a previous call
+ // therefore, we need to..
+ // i) find the parent cat
+ // ii) ensure it already has a faux-disco container
+ // iii) ensure that disco container doesn't already contain a disco-entry for this subcat
+ // iv) create the dummy app folder by pushing the disco into the apprefs as normal
+ // v) create a dummy '..' for going back up, in the child
+
+ mm_category_t *pcat = pnd_box_find_by_key ( m_categories, parentcatname );
+
+ if ( ! pcat -> disco ) {
+ pcat -> disco = pnd_box_new ( pcat -> catname );
+ }
+
+ // if this subcat is already in the faux-disco list, then its probably already
+ // handled so we needn't concern ourselves anymore. If not, then we can
+ // create it and push it into the parent as a new 'app'
+ pnd_disco_t *disco = pnd_box_find_by_key ( pcat -> disco, catname );
+
+ if ( ! disco ) {
+
+ disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
+ if ( disco ) {
+
+ // create the subcat faux-disco entry, and register into parent cat .. if its visible
+ char uid [ 30 ];
+ sprintf ( uid, "%p", catname );
+
+ disco -> unique_id = strdup ( uid );
+ if ( strchr ( catname, '.' ) ) {
+ disco -> title_en = strdup ( strchr ( catname, '.' ) + 1 );
+ } else {
+ disco -> title_en = strdup ( catname );
+ }
+ disco -> object_flags = PND_DISCO_GENERATED;
+ disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
+ disco -> object_path = strdup ( catname );
+
+ category_push ( parentcatname, NULL /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
+
+ // create .. faux-disco entry into child cat
+ disco = pnd_box_allocinsert ( pcat -> disco, catname, sizeof(pnd_disco_t) );
+
+ sprintf ( uid, "%p", uid );
+
+ disco -> unique_id = strdup ( uid );
+ disco -> title_en = strdup ( ".." );
+ disco -> object_flags = PND_DISCO_GENERATED;
+ disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
+
+ category_push ( catname, parentcatname /* parent cat */, disco, 0 /*ovrh*/, NULL /* fspath */, 1 /* visible */ );
+
+ } // making faux disco entries
+
+ } // disco already exist?
+
+ } // subcat as folder?
+
+ // hack :(
+ meta_done:
+
+ //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
+
+ return ( 1 );
+}
+
+unsigned char category_fs_restock ( mm_category_t *cat ) {
+
+ if ( ! cat -> fspath ) {
+ return ( 1 ); // not a filesystem browser tab
+ }
+
+ // clear any existing baggage
+ //
+
+ // apprefs
+ mm_appref_t *iter = cat -> refs, *next;
+ while ( iter ) {
+ next = iter -> next;
+ free ( iter );
+ iter = next;
+ }
+ cat -> refs = NULL;
+
+ // discos
+ if ( cat -> disco ) {
+ pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
+ pnd_disco_t *n;
+ while ( p ) {
+ n = pnd_box_get_next ( p );
+ pnd_disco_destroy ( p );
+ p = n;
+ }
+ pnd_box_delete ( cat -> disco );
+ }
+
+ // rescan the filesystem
+ //
+
+ //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
+ DIR *d;
+
+ if ( ( d = opendir ( cat -> fspath ) ) ) {
+ struct dirent *de = readdir ( d );
+
+ pnd_disco_t *disco;
+ char uid [ 100 ];
+
+ cat -> disco = pnd_box_new ( cat -> catname );
+
+ while ( de ) {
+
+ struct stat buffy;
+ char fullpath [ PATH_MAX ];
+ sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
+ int statret = stat ( fullpath, &buffy );
+
+ // if file is executable somehow or another
+ if ( statret == 0 &&
+ buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
+ )
+ {
+ // determine unique-id
+ sprintf ( uid, "%d", (int) de -> d_ino );
+ disco = NULL;
+
+ switch ( de -> d_type ) {
+
+ case DT_DIR:
+ if ( strcmp ( de -> d_name, "." ) == 0 ) {
+ // ignore ".", but ".." is fine
+ } else if ( strcmp ( de -> d_name, ".." ) == 0 && strcmp ( cat -> fspath, "/" ) == 0 ) {
+ // ignore ".." only if we're at the true root
+ } else {
+ disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
+ disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
+ }
+ break;
+ case DT_UNKNOWN:
+ case DT_REG:
+ disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
+ disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
+ break;
+
+ } // switch
+
+ // found a directory or executable?
+ if ( disco ) {
+ // register with current category
+ disco -> unique_id = strdup ( uid );
+ disco -> title_en = strdup ( de -> d_name );
+ disco -> object_flags = PND_DISCO_GENERATED;
+ disco -> object_path = strdup ( cat -> fspath );
+ disco -> object_filename = strdup ( de -> d_name );
+ category_push ( cat -> catname, NULL /* parent cat */, disco, 0 /* no ovr */, NULL /* fspath already set */, 1 /* visible */ );
+ // if a override icon exists, cache it up
+ cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
+ pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
+ }
+
+ } // stat
+
+ // next
+ de = readdir ( d );
+ }
+
+ closedir ( d );
+ }
+
+ return ( 1 );
+}
+
+static int catname_cmp ( const void *p1, const void *p2 ) {
+ //mm_category_t *c1 = (mm_category_t*) p1;
+ //mm_category_t *c2 = (mm_category_t*) p2;
+ mm_category_t *c1 = *( (mm_category_t**) p1 );
+ mm_category_t *c2 = *( (mm_category_t**) p2 );
+
+ if ( ( isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
+ return ( -1 );
+ } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( isalnum ( c1 -> catname [ 1 ] ) ) ) {
+ return ( 1 );
+ } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
+ return ( 0 );
+ }
+
+ int i = strcasecmp ( c1 -> catname, c2 -> catname );
+ //printf ( "cat name compare %p %s to %p %s = %d\n", p1, c1 -> catname, p2, c2 -> catname, i );
+
+ return ( i );
+}
+
+void category_sort ( void ) {
+ // we probably don't want to sort tab categories, since the user may have specified an ordering
+ // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
+
+#if 0
+ qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
+#endif
+
+#if 1
+ qsort ( g_categories, g_categorycount, sizeof(mm_category_t*), catname_cmp );
+#endif
+
+ return;
+}
+
+void category_publish ( unsigned int filter_mask, char *param ) {
+ unsigned char interested;
+
+ // clear published categories
+ memset ( g_categories, '\0', sizeof(mm_category_t*) * MAX_CATS );
+ g_categorycount = 0;
+
+ // figure out the start
+ mm_category_t *iter = pnd_box_get_head ( m_categories );
+
+ // for each category we know...
+ while ( iter ) {
+
+ interested = 0;
+
+ // is this category desired?
+ if ( filter_mask == CFALL ) {
+ interested = 1;
+ } else if ( filter_mask == CFBYNAME ) {
+ if ( strcasecmp ( iter -> catname, param ) == 0 ) {
+ interested = 1;
+ }
+ } else if ( iter -> catflags == filter_mask ) {
+ interested = 1;
+ } // if
+
+ if ( interested ) {
+ // set us up the bomb; notice that we're just duplicating the pointers, not making
+ // any new data here; none of this should ever be free'd!
+ g_categories [ g_categorycount ] = iter;
+ g_categorycount++;
+ }
+
+ // next
+ iter = pnd_box_get_next ( iter );
+ }
+
+ // dump
+#if 0
+ unsigned int i;
+ for ( i = 0; i < g_categorycount; i++ ) {
+ printf ( "Unsorted cat %d %p: %s\n", i, &(g_categories [ i ]), g_categories [ i ] -> catname );
+ }
+#endif
+
+ // sort published categories
+ category_sort();
+
+ return;
+}
+
+unsigned int category_count ( unsigned int filter_mask ) {
+ mm_category_t *iter = pnd_box_get_head ( m_categories );
+ unsigned int count = 0;
+
+ // for each category we know...
+ while ( iter ) {
+
+ // is this category desired?
+ if ( iter -> catflags == filter_mask ) {
+ count++;
+ } // if
+
+ // next
+ iter = pnd_box_get_next ( iter );
+ }
+
+ return ( count );
+}
+
+int category_index ( char *catname ) {
+ unsigned char i;
+
+ for ( i = 0; i < g_categorycount; i++ ) {
+
+ if ( strcasecmp ( g_categories [ i ] -> catname, catname ) == 0 ) {
+ return ( i );
+ }
+
+ }
+
+ return ( -1 );
+}
+
+unsigned char category_contains_app ( char *catname, char *unique_id ) {
+
+ mm_category_t *c = pnd_box_find_by_key ( m_categories, catname );
+
+ if ( ! c ) {
+ return ( 0 ); // wtf?
+ }
+
+ if ( ! c -> refs ) {
+ return ( 0 ); // no apps at all
+ }
+
+ mm_appref_t *iter = c -> refs;
+
+ while ( iter ) {
+
+ if ( strcmp ( iter -> ref -> unique_id, unique_id ) == 0 ) {
+ return ( 1 );
+ }
+
+ iter = iter -> next;
+ }
+
+ return ( 0 );