Merge branch 'master' of ssh://skeezixgit@git.openpandora.org/srv/git/pandora-libraries
[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
10 #include "pnd_conf.h"
11 #include "pnd_logger.h"
12 #include "pnd_pxml.h"
13 #include "pnd_container.h"
14 #include "pnd_discovery.h"
15 #include "../lib/pnd_pathiter.h"
16
17 #include "mmenu.h"
18 #include "mmcache.h"
19 #include "mmcat.h"
20
21 mm_category_t _categories [ MAX_CATS ];
22 mm_category_t *g_categories = _categories;
23 unsigned char g_categorycount = 0;
24
25 mm_category_t _categories_invis [ MAX_CATS ];
26 unsigned char _categories_inviscount = 0;
27
28 mm_catmap_t g_catmaps [ MAX_CATS ];
29 unsigned char g_catmapcount = 0;
30
31 extern pnd_conf_handle g_conf;
32
33 unsigned char category_push ( char *catname, pnd_disco_t *app, pnd_conf_handle ovrh, char *fspath ) {
34   mm_category_t *c;
35
36   // check category list; if found, append app to the end of it.
37   // if not found, add it to the category list and plop the app in there.
38   // app's are just app-refs, which contain links to the disco-t list -- thus removal is only in one place, and
39   // an app can be in multiple categories if we like..
40   //
41
42   // find or create category
43   //
44
45   if ( ( c = category_query ( catname ) ) ) {
46     // category was found..
47   } else {
48     // category wasn't found..
49     //pnd_log ( PND_LOG_DEFAULT, "New category '%s'\n", catname );
50     g_categories [ g_categorycount ].catname = strdup ( catname );
51     g_categories [ g_categorycount ].refs = NULL;
52     c = &(g_categories [ g_categorycount ]);
53
54     if ( fspath ) {
55       g_categories [ g_categorycount ].fspath = strdup ( fspath );;
56     }
57
58     g_categorycount++;
59   }
60
61   if ( ! app ) {
62     return ( 1 ); // create cat, but skip app
63   }
64
65   // alloc and populate appref
66   //
67   mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
68   if ( ! ar ) {
69     return ( 0 );
70   }
71
72   bzero ( ar, sizeof(mm_appref_t) );
73
74   ar -> ref = app;
75   ar -> ovrh = ovrh;
76
77   // plug it into category
78   //   and sort it on insert!
79 #if 0 // no sorting
80   ar -> next = c -> refs;
81   c -> refs = ar;
82 #else // with sorting
83   // if no refs at all, or new guy has no title, just stick it in at head
84   if ( c -> refs && ar -> ref -> title_en ) {
85     mm_appref_t *iter = c -> refs;
86     mm_appref_t *last = NULL;
87
88     while ( iter ) {
89
90       if ( iter -> ref -> title_en ) {
91         if ( cat_sort_score ( ar, iter ) < 0 ) {
92           // new guy is smaller than the current guy!
93           break;
94         }
95       } else {
96         // since new guy must have a name by here, we're bigger than any guy who does not have a name
97         // --> continue
98       }
99
100       last = iter;
101       iter = iter -> next;
102     }
103
104     if ( iter ) {
105       // smaller than the current guy, so stitch in
106       if ( last ) {
107         ar -> next = iter;
108         last -> next = ar;
109       } else {
110         ar -> next = c -> refs;
111         c -> refs = ar;
112       }
113     } else {
114       // we're the biggest, just append to last
115       last -> next = ar;
116     }
117
118   } else {
119     ar -> next = c -> refs;
120     c -> refs = ar;
121   }
122 #endif
123   c -> refcount++;
124
125   return ( 1 );
126 }
127
128 mm_category_t *category_query ( char *catname ) {
129   unsigned char i;
130
131   for ( i = 0; i < g_categorycount; i++ ) {
132
133     if ( strcasecmp ( g_categories [ i ].catname, catname ) == 0 ) {
134       return ( &(g_categories [ i ]) );
135     }
136
137   }
138
139   return ( NULL );
140 }
141
142 int cat_sort_score ( mm_appref_t *s1, mm_appref_t *s2 ) {
143
144   extern unsigned char ui_category;
145
146   // are we in a directory browser, or looking at pnd-files?
147   if ( g_categories [ ui_category ].fspath ) {
148
149     if ( s1 == s2 ) {
150       return ( 0 ); // equal
151
152     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
153                 s2 -> ref -> object_type == pnd_object_type_directory )
154     {
155       // both are directories, be nice
156       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
157     } else if ( s1 -> ref -> object_type == pnd_object_type_directory &&
158                 s2 -> ref -> object_type != pnd_object_type_directory )
159     {
160       return ( -1 ); // dir on the left is earlier than file on the right
161     } else if ( s1 -> ref -> object_type != pnd_object_type_directory &&
162                 s2 -> ref -> object_type == pnd_object_type_directory )
163     {
164       return ( 1 ); // dir on the right is earlier than file on the left
165     } else {
166       // file on file
167       return ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
168     }
169
170   }
171
172   return ( strcasecmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
173 }
174
175 void category_dump ( void ) {
176   unsigned int i;
177
178   // WHY AREN'T I SORTING ON INSERT?
179
180   // dump
181   for ( i = 0; i < g_categorycount; i++ ) {
182     pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", i, g_categories [ i ].catname, g_categories [ i ].refcount );
183     mm_appref_t *ar = g_categories [ i ].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   } // for
191
192   return;
193 }
194
195 static void _category_freeall ( mm_category_t *p, unsigned int c ) {
196   unsigned int i;
197   mm_appref_t *iter, *next;
198
199   for ( i = 0; i < c; i++ ) {
200
201     iter = p [ i ].refs;
202
203     while ( iter ) {
204       next = iter -> next;
205       free ( iter );
206       iter = next;
207     }
208
209     p [ i ].refs = NULL;
210
211     if ( p [ i ].catname ) {
212       free ( p [ i ].catname );
213       p [ i ].catname = NULL;
214     }
215
216     if ( p [ i ].fspath ) {
217       free ( p [ i ].fspath );
218       p [ i ].fspath = NULL;
219     }
220
221   } // for
222
223   return;
224 }
225
226 void category_freeall ( void ) {
227
228   _category_freeall ( g_categories, g_categorycount );
229   _category_freeall ( _categories_invis, _categories_inviscount );
230
231   g_categorycount = 0;
232   _categories_inviscount = 0;
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 */ );
259         g_catmaps [ g_catmapcount ].target = category_query ( 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   unsigned char catcount = g_categorycount;
289   char catnamebuffer [ 512 ] = "";
290
291   if ( ! catname ) {
292     return ( 1 ); // fine, just nada
293   }
294
295   //fprintf ( stderr, "meta push: '%s'\n", catname );
296
297   if ( ! visiblep ) {
298     //return ( 1 ); // fine, suppress it
299
300     // serious evidence this was a rushed program
301     g_categories = _categories_invis;
302     g_categorycount = _categories_inviscount;
303
304     // if invisible, and a parent category name is known, prepend it for ease of use
305     if ( parentcatname ) {
306       snprintf ( catnamebuffer, 500, "%s.%s", parentcatname, catname );
307       catname = catnamebuffer;
308     }
309
310   }
311
312   // do we honour cat mapping at all?
313   if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
314
315     // is this guy mapped?
316     cat = category_map_query ( catname );
317
318     if ( cat ) {
319       category_push ( cat -> catname, app, ovrh, NULL /* fspath */ );
320       goto visibility_hack_cleanup;
321     }
322
323     // not mapped.. but default?
324     if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
325       char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
326       if ( def ) {
327         category_push ( def, app, ovrh, NULL /* fspath */ );
328         goto visibility_hack_cleanup;
329       }
330     }
331
332   } // cat map is desired?
333
334   // not default, just do it
335   category_push ( catname, app, ovrh, NULL /* fspath */ );
336
337   // hack :(
338  visibility_hack_cleanup:
339   if ( ! visiblep ) {
340     _categories_inviscount = g_categorycount;
341     g_categories = _categories;
342     g_categorycount = catcount;
343   }
344
345   //fprintf ( stderr, "cat meta-push : vis[%30s,%d b] : tally; vis %d invis %d\n", catname, visiblep, g_categorycount, _categories_inviscount );
346
347   return ( 1 );
348 }
349
350 unsigned char category_fs_restock ( mm_category_t *cat ) {
351
352   if ( ! cat -> fspath ) {
353     return ( 1 ); // not a filesystem browser tab
354   }
355
356   // clear any existing baggage
357   //
358
359   // apprefs
360   mm_appref_t *iter = cat -> refs, *next;
361   while ( iter ) {
362     next = iter -> next;
363     free ( iter );
364     iter = next;
365   }
366   cat -> refs = NULL;
367
368   // discos
369   if ( cat -> disco ) {
370     pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
371     pnd_disco_t *n;
372     while ( p ) {
373       n = pnd_box_get_next ( p );
374       pnd_disco_destroy ( p );
375       p = n;
376     }
377     pnd_box_delete ( cat -> disco );
378   }
379
380   // rescan the filesystem
381   //
382
383   //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
384   DIR *d;
385
386   if ( ( d = opendir ( cat -> fspath ) ) ) {
387     struct dirent *de = readdir ( d );
388
389     pnd_disco_t *disco;
390     char uid [ 100 ];
391
392     cat -> disco = pnd_box_new ( cat -> catname );
393
394     while ( de ) {
395
396       struct stat buffy;
397       char fullpath [ PATH_MAX ];
398       sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
399       int statret = stat ( fullpath, &buffy );
400
401       // if file is executable somehow or another
402       if ( statret == 0 &&
403            buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
404          )
405       {
406         // determine unique-id
407         sprintf ( uid, "%d", (int) de -> d_ino );
408         disco = NULL;
409
410         switch ( de -> d_type ) {
411
412         case DT_DIR:
413           if ( strcmp ( de -> d_name, "." ) == 0 ) {
414             // ignore ".", but ".." is fine
415           } else {
416             disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
417             disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
418           }
419           break;
420         case DT_UNKNOWN:
421         case DT_REG:
422           disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
423           disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
424           break;
425
426         } // switch
427
428         // found a directory or executable?
429         if ( disco ) {
430           // register with this category
431           disco -> unique_id = strdup ( uid );
432           disco -> title_en = strdup ( de -> d_name );
433           disco -> object_flags = PND_DISCO_GENERATED;
434           disco -> object_path = strdup ( cat -> fspath );
435           disco -> object_filename = strdup ( de -> d_name );
436           category_push ( cat -> catname, disco, 0, NULL /* fspath already set */ );
437           // if a override icon exists, cache it up
438           cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
439                        pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
440         }
441
442       } // stat
443
444       // next
445       de = readdir ( d );
446     }
447
448     closedir ( d );
449   }
450
451   return ( 1 );
452 }
453
454 static int catname_cmp ( const void *p1, const void *p2 ) {
455   mm_category_t *c1 = (mm_category_t*) p1;
456   mm_category_t *c2 = (mm_category_t*) p2;
457   return ( strcasecmp ( c1 -> catname, c2 -> catname ) );
458 }
459
460 void category_sort ( void ) {
461   // we probably don't want to sort tab categories, since the user may have specified an ordering
462   // But we can sort invisi-cats, to make them easier to find, and ordered by parent category
463
464   qsort ( _categories_invis, _categories_inviscount, sizeof(mm_category_t), catname_cmp );
465
466   return;
467 }