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