Added config option so can force 'bad categories' into Other using freedesktop list
[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, 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 ( visiblep ) {
62       c -> catflags = CFNORMAL;
63     } else {
64       c -> catflags = CFHIDDEN;
65     }
66     c -> refs = NULL;
67
68     if ( fspath ) {
69       c -> fspath = strdup ( fspath );
70     }
71
72     m_categorycount++;
73   }
74
75   if ( ! app ) {
76     return ( 1 ); // create cat, but skip app
77   }
78
79   // alloc and populate appref
80   //
81   mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
82   if ( ! ar ) {
83     return ( 0 );
84   }
85
86   bzero ( ar, sizeof(mm_appref_t) );
87
88   ar -> ref = app;
89   ar -> ovrh = ovrh;
90
91   // plug it into category
92   //   and sort it on insert!
93 #if 0 // no sorting
94   ar -> next = c -> refs;
95   c -> refs = ar;
96 #else // with sorting
97   // if no refs at all, or new guy has no title, just stick it in at head
98   if ( c -> refs && ar -> ref -> title_en ) {
99     mm_appref_t *iter = c -> refs;
100     mm_appref_t *last = NULL;
101
102     while ( iter ) {
103
104       if ( iter -> ref -> title_en ) {
105         if ( cat_sort_score ( c, ar, iter ) < 0 ) {
106           // new guy is smaller than the current guy!
107           break;
108         }
109       } else {
110         // since new guy must have a name by here, we're bigger than any guy who does not have a name
111         // --> continue
112       }
113
114       last = iter;
115       iter = iter -> next;
116     }
117
118     if ( iter ) {
119       // smaller than the current guy, so stitch in
120       if ( last ) {
121         ar -> next = iter;
122         last -> next = ar;
123       } else {
124         ar -> next = c -> refs;
125         c -> refs = ar;
126       }
127     } else {
128       // we're the biggest, just append to last
129       last -> next = ar;
130     }
131
132   } else {
133     ar -> next = c -> refs;
134     c -> refs = ar;
135   }
136 #endif
137   c -> refcount++;
138
139   return ( 1 );
140 }
141
142 int cat_sort_score ( mm_category_t *cat, mm_appref_t *s1, mm_appref_t *s2 ) {
143
144   // are we in a directory browser, or looking at pnd-files?
145   if ( cat -> fspath ) {
146
147     if ( s1 == s2 ) {
148       return ( 0 ); // equal
149
150     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
151                 s2 -> ref -> object_type == pnd_object_type_directory )
152     {
153       // both are directories, be nice
154       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
155     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
156                 s2 -> ref -> object_type != pnd_object_type_directory )
157     {
158       return ( -1 ); // dir on the left is earlier than file on the right
159     } else if ( s1 -> ref -> object_type != pnd_object_type_directory &&
160                 s2 -> ref -> object_type == pnd_object_type_directory )
161     {
162       return ( 1 ); // dir on the right is earlier than file on the left
163     } else {
164       // file on file
165       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
166     }
167
168   }
169
170   return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
171 }
172
173 void category_dump ( void ) {
174
175   // WHY AREN'T I SORTING ON INSERT?
176
177   // dump
178   mm_category_t *iter = pnd_box_get_head ( m_categories );
179   unsigned int counter = 0;
180
181   while ( iter ) {
182
183     pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", counter, iter -> catname, iter -> refcount );
184     mm_appref_t *ar = iter -> refs;
185
186     while ( ar ) {
187       pnd_log ( PND_LOG_DEFAULT, "  Appref %s\n", IFNULL(ar -> ref -> title_en,"No Name") );
188       ar = ar -> next;
189     }
190
191     iter = pnd_box_get_next ( iter );
192     counter++;
193   }
194
195   return;
196 }
197
198 void category_freeall ( void ) {
199   mm_category_t *c, *cnext;
200   mm_appref_t *iter, *next;
201
202   c = pnd_box_get_head ( m_categories );
203
204   while ( c ) {
205     cnext = pnd_box_get_next ( c );
206
207     // wipe 'em
208     iter = c -> refs;
209
210     while ( iter ) {
211       next = iter -> next;
212       free ( iter );
213       iter = next;
214     }
215
216     c -> refs = NULL;
217
218     if ( c -> catname ) {
219       free ( c -> catname );
220       c -> catname = NULL;
221     }
222
223     if ( c -> fspath ) {
224       free ( c -> fspath );
225       c -> fspath = NULL;
226     }
227
228     pnd_box_delete_node ( m_categories, c );
229
230     // next
231     c = cnext;
232   }
233
234   return;
235 }
236
237 unsigned char category_map_setup ( void ) {
238
239   char *searchpath = pnd_box_get_head ( g_conf );
240
241   if ( ! searchpath ) {
242     return ( 0 );
243   }
244
245   // look through conf for useful keys
246   while ( searchpath ) {
247     char *k = pnd_box_get_key ( searchpath );
248
249     // does this key look like a category mapping key?
250     if ( strncasecmp ( k, "categories.@", 12 ) == 0 ) {
251       k += 12;
252
253       // iterate across 'words' in v, assigning catmaps to them
254       SEARCHCHUNK_PRE
255       {
256         //pnd_log ( pndn_debug, "target(%s) from(%s)\n", k, buffer );
257
258         category_push ( k, NULL, 0, NULL /* fspath */, 1 );
259         g_catmaps [ g_catmapcount ].target = pnd_box_find_by_key ( m_categories, k );
260         g_catmaps [ g_catmapcount ].from = strdup ( buffer );
261         g_catmapcount++;
262
263       }
264       SEARCHCHUNK_POST
265
266     } // if key looks like catmap
267
268     searchpath = pnd_box_get_next ( searchpath );
269   } // while each conf key
270
271   return ( 1 );
272 }
273
274 mm_category_t *category_map_query ( char *cat ) {
275   unsigned char i;
276
277   for ( i = 0; i < g_catmapcount; i++ ) {
278     if ( strcasecmp ( g_catmaps [ i ].from, cat ) == 0 ) {
279       return ( g_catmaps [ i ].target );
280     }
281   }
282
283   return ( NULL );
284 }
285
286 unsigned char category_meta_push ( char *catname, char *parentcatname, pnd_disco_t *app, pnd_conf_handle ovrh, unsigned char visiblep ) {
287   mm_category_t *cat;
288   char catnamebuffer [ 512 ] = "";
289
290   if ( ! catname ) {
291     return ( 1 ); // fine, just nada
292   }
293
294   //fprintf ( stderr, "meta push: '%s'\n", catname );
295
296   // push bad categories into Other (if we're not targeting All right now)
297   if ( pnd_conf_get_as_int_d ( g_conf, "categories.good_cats_only", 1 ) ) {
298
299     // don't audit All
300     if ( strncmp ( catname, "All ", 4 ) != 0 ) {
301
302       // if this is a parent cat..
303       if ( catname && ! parentcatname ) {
304
305         // if bad, shove it to Other
306         if ( ! freedesktop_check_cat ( catname ) ) {
307           parentcatname = NULL;
308           catname = BADCATNAME;
309           visiblep = cat_is_visible ( g_conf, catname );
310         }
311
312       } else if ( catname && parentcatname ) {
313         // this is a subcat
314
315         // if parent is bad, then we probably already pushed it over, so don't do it again.
316         // if its parent is okay, but subcat is bad, push it to other. (ie: lets avoid duplication in Other)
317         if ( ! freedesktop_check_cat ( parentcatname ) ) {
318           // skip
319           return ( 1 );
320
321         } else if ( ! freedesktop_check_cat ( catname ) ) {
322           parentcatname = NULL;
323           catname = BADCATNAME;
324           visiblep = cat_is_visible ( g_conf, catname );
325         }
326
327       } // parent or child cat?
328
329     } // not All
330
331   } // good cats only?
332
333   // if invisible, and a parent category name is known, prepend it for ease of use
334   if ( ! visiblep && parentcatname ) {
335     snprintf ( catnamebuffer, 500, "%s.%s", parentcatname, catname );
336     catname = catnamebuffer;
337   }
338
339   // do we honour cat mapping at all?
340   if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
341
342     // is this guy mapped?
343     cat = category_map_query ( catname );
344
345     if ( cat ) {
346       category_push ( cat -> catname, app, ovrh, NULL /* fspath */, visiblep );
347       goto meta_done;
348     }
349
350     // not mapped.. but default?
351     if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
352       char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
353       if ( def ) {
354         category_push ( def, app, ovrh, NULL /* fspath */, visiblep );
355         goto meta_done;
356       }
357     }
358
359   } // cat map is desired?
360
361   // not default, just do it
362   category_push ( catname, app, ovrh, NULL /* fspath */, visiblep );
363
364   // hack :(
365  meta_done:
366
367   //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
368
369   return ( 1 );
370 }
371
372 unsigned char category_fs_restock ( mm_category_t *cat ) {
373
374   if ( ! cat -> fspath ) {
375     return ( 1 ); // not a filesystem browser tab
376   }
377
378   // clear any existing baggage
379   //
380
381   // apprefs
382   mm_appref_t *iter = cat -> refs, *next;
383   while ( iter ) {
384     next = iter -> next;
385     free ( iter );
386     iter = next;
387   }
388   cat -> refs = NULL;
389
390   // discos
391   if ( cat -> disco ) {
392     pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
393     pnd_disco_t *n;
394     while ( p ) {
395       n = pnd_box_get_next ( p );
396       pnd_disco_destroy ( p );
397       p = n;
398     }
399     pnd_box_delete ( cat -> disco );
400   }
401
402   // rescan the filesystem
403   //
404
405   //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
406   DIR *d;
407
408   if ( ( d = opendir ( cat -> fspath ) ) ) {
409     struct dirent *de = readdir ( d );
410
411     pnd_disco_t *disco;
412     char uid [ 100 ];
413
414     cat -> disco = pnd_box_new ( cat -> catname );
415
416     while ( de ) {
417
418       struct stat buffy;
419       char fullpath [ PATH_MAX ];
420       sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
421       int statret = stat ( fullpath, &buffy );
422
423       // if file is executable somehow or another
424       if ( statret == 0 &&
425            buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
426          )
427       {
428         // determine unique-id
429         sprintf ( uid, "%d", (int) de -> d_ino );
430         disco = NULL;
431
432         switch ( de -> d_type ) {
433
434         case DT_DIR:
435           if ( strcmp ( de -> d_name, "." ) == 0 ) {
436             // ignore ".", but ".." is fine
437           } else {
438             disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
439             disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
440           }
441           break;
442         case DT_UNKNOWN:
443         case DT_REG:
444           disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
445           disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
446           break;
447
448         } // switch
449
450         // found a directory or executable?
451         if ( disco ) {
452           // register with current category
453           disco -> unique_id = strdup ( uid );
454           disco -> title_en = strdup ( de -> d_name );
455           disco -> object_flags = PND_DISCO_GENERATED;
456           disco -> object_path = strdup ( cat -> fspath );
457           disco -> object_filename = strdup ( de -> d_name );
458           category_push ( cat -> catname, disco, 0 /* no ovr */, NULL /* fspath already set */, 1 /* visible */ );
459           // if a override icon exists, cache it up
460           cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
461                        pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
462         }
463
464       } // stat
465
466       // next
467       de = readdir ( d );
468     }
469
470     closedir ( d );
471   }
472
473   return ( 1 );
474 }
475
476 static int catname_cmp ( const void *p1, const void *p2 ) {
477   //mm_category_t *c1 = (mm_category_t*) p1;
478   //mm_category_t *c2 = (mm_category_t*) p2;
479   mm_category_t *c1 = *( (mm_category_t**) p1 );
480   mm_category_t *c2 = *( (mm_category_t**) p2 );
481
482   if ( ( isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
483     return ( -1 );
484   } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( isalnum ( c1 -> catname [ 1 ] ) ) ) {
485     return ( 1 );
486   } else if ( ( ! isalnum ( c1 -> catname [ 0 ] ) ) && ( ! isalnum ( c1 -> catname [ 1 ] ) ) ) {
487     return ( 0 );
488   }
489
490   int i = strcasecmp ( c1 -> catname, c2 -> catname );
491   //printf ( "cat name compare %p %s to %p %s = %d\n", p1, c1 -> catname, p2, c2 -> catname, i );
492
493   return ( i );
494 }
495
496 void category_sort ( void ) {
497   // we probably don't want to sort tab categories, since the user may have specified an ordering
498   // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
499
500 #if 0
501   qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
502 #endif
503
504 #if 1
505   qsort ( g_categories, g_categorycount, sizeof(mm_category_t*), catname_cmp );
506 #endif
507
508   return;
509 }
510
511 void category_publish ( unsigned int filter_mask, char *param ) {
512   unsigned char interested;
513
514   // clear published categories
515   memset ( g_categories, '\0', sizeof(mm_category_t*) * MAX_CATS );
516   g_categorycount = 0;
517
518   // figure out the start
519   mm_category_t *iter = pnd_box_get_head ( m_categories );
520
521   // for each category we know...
522   while ( iter ) {
523
524     interested = 0;
525
526     // is this category desired?
527     if ( filter_mask == CFALL ) {
528       interested = 1;
529     } else if ( iter -> catflags == filter_mask ) {
530       interested = 1;
531     } // if
532
533     if ( interested ) {
534       // set us up the bomb; notice that we're just duplicating the pointers, not making
535       // any new data here; none of this should ever be free'd!
536       g_categories [ g_categorycount ] = iter;
537       g_categorycount++;
538     }
539
540     // next
541     iter = pnd_box_get_next ( iter );
542   }
543
544   // dump
545 #if 0
546   unsigned int i;
547   for ( i = 0; i < g_categorycount; i++ ) {
548     printf ( "Unsorted cat %d %p: %s\n", i, &(g_categories [ i ]), g_categories [ i ] -> catname );
549   }
550 #endif
551
552   // sort published categories
553   category_sort();
554
555   return;
556 }
557
558 unsigned int category_count ( unsigned int filter_mask ) {
559   mm_category_t *iter = pnd_box_get_head ( m_categories );
560   unsigned int count = 0;
561
562   // for each category we know...
563   while ( iter ) {
564
565     // is this category desired?
566     if ( iter -> catflags == filter_mask ) {
567       count++;
568     } // if
569
570     // next
571     iter = pnd_box_get_next ( iter );
572   }
573
574   return ( count );
575 }
576
577 int category_index ( char *catname ) {
578   unsigned char i;
579  
580   for ( i = 0; i < g_categorycount; i++ ) {
581  
582     if ( strcasecmp ( g_categories [ i ] -> catname, catname ) == 0 ) {
583       return ( i );
584     }
585  
586   }
587  
588   return ( -1 );
589 }