minimenu:
[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 g_categories [ MAX_CATS ];
22 unsigned char g_categorycount = 0;
23
24 mm_catmap_t g_catmaps [ MAX_CATS ];
25 unsigned char g_catmapcount = 0;
26
27 extern pnd_conf_handle g_conf;
28
29 unsigned char category_push ( char *catname, pnd_disco_t *app, pnd_conf_handle ovrh, char *fspath ) {
30   mm_category_t *c;
31
32   // check category list; if found, append app to the end of it.
33   // if not found, add it to the category list and plop the app in there.
34   // app's are just app-refs, which contain links to the disco-t list -- thus removal is only in one place, and
35   // an app can be in multiple categories if we like..
36   //
37
38   // find or create category
39   //
40
41   if ( ( c = category_query ( catname ) ) ) {
42     // category was found..
43   } else {
44     // category wasn't found..
45     pnd_log ( PND_LOG_DEFAULT, "New category '%s'\n", catname );
46     g_categories [ g_categorycount ].catname = strdup ( catname );
47     g_categories [ g_categorycount ].refs = NULL;
48     c = &(g_categories [ g_categorycount ]);
49
50     if ( fspath ) {
51       g_categories [ g_categorycount ].fspath = strdup ( fspath );;
52     }
53
54     g_categorycount++;
55   }
56
57   if ( ! app ) {
58     return ( 1 ); // create cat, but skip app
59   }
60
61   // alloc and populate appref
62   //
63   mm_appref_t *ar = malloc ( sizeof(mm_appref_t) );
64   if ( ! ar ) {
65     return ( 0 );
66   }
67
68   bzero ( ar, sizeof(mm_appref_t) );
69
70   ar -> ref = app;
71   ar -> ovrh = ovrh;
72
73   // plug it into category
74   //   and sort it on insert!
75 #if 0 // no sorting
76   ar -> next = c -> refs;
77   c -> refs = ar;
78 #else // with sorting
79   // if no refs at all, or new guy has no title, just stick it in at head
80   if ( c -> refs && ar -> ref -> title_en ) {
81     mm_appref_t *iter = c -> refs;
82     mm_appref_t *last = NULL;
83
84     while ( iter ) {
85
86       if ( iter -> ref -> title_en ) {
87         if ( cat_sort_score ( ar, iter ) < 0 ) {
88           // new guy is smaller than the current guy!
89           break;
90         }
91       } else {
92         // since new guy must have a name by here, we're bigger than any guy who does not have a name
93         // --> continue
94       }
95
96       last = iter;
97       iter = iter -> next;
98     }
99
100     if ( iter ) {
101       // smaller than the current guy, so stitch in
102       if ( last ) {
103         ar -> next = iter;
104         last -> next = ar;
105       } else {
106         ar -> next = c -> refs;
107         c -> refs = ar;
108       }
109     } else {
110       // we're the biggest, just append to last
111       last -> next = ar;
112     }
113
114   } else {
115     ar -> next = c -> refs;
116     c -> refs = ar;
117   }
118 #endif
119   c -> refcount++;
120
121   return ( 1 );
122 }
123
124 mm_category_t *category_query ( char *catname ) {
125   unsigned char i;
126
127   for ( i = 0; i < g_categorycount; i++ ) {
128
129     if ( strcasecmp ( g_categories [ i ].catname, catname ) == 0 ) {
130       return ( &(g_categories [ i ]) );
131     }
132
133   }
134
135   return ( NULL );
136 }
137
138 int cat_sort_score ( mm_appref_t *s1, mm_appref_t *s2 ) {
139
140   extern mm_category_t g_categories [ MAX_CATS ];
141   extern unsigned char g_categorycount;
142   extern unsigned char ui_category;
143
144   // are we in a directory browser, or looking at pnd-files?
145   if ( g_categories [ ui_category ].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 ( strcmp ( s1 -> ref -> title_en, s2 -> ref -> title_en ) );
171 }
172
173 void category_dump ( void ) {
174   unsigned int i;
175
176   // WHY AREN'T I SORTING ON INSERT?
177
178   // dump
179   for ( i = 0; i < g_categorycount; i++ ) {
180     pnd_log ( PND_LOG_DEFAULT, "Category %u: '%s' * %u\n", i, g_categories [ i ].catname, g_categories [ i ].refcount );
181     mm_appref_t *ar = g_categories [ i ].refs;
182
183     while ( ar ) {
184       pnd_log ( PND_LOG_DEFAULT, "  Appref %s\n", IFNULL(ar -> ref -> title_en,"No Name") );
185       ar = ar -> next;
186     }
187
188   } // for
189
190   return;
191 }
192
193 void category_freeall ( void ) {
194   unsigned int i;
195   mm_appref_t *iter, *next;
196
197   for ( i = 0; i < g_categorycount; i++ ) {
198
199     iter = g_categories [ i ].refs;
200
201     while ( iter ) {
202       next = iter -> next;
203       free ( iter );
204       iter = next;
205     }
206
207     g_categories [ i ].refs = NULL;
208
209   } // for
210
211   g_categorycount = 0;
212
213   return;
214 }
215
216 unsigned char category_map_setup ( void ) {
217
218   char *searchpath = pnd_box_get_head ( g_conf );
219
220   if ( ! searchpath ) {
221     return ( 0 );
222   }
223
224   // look through conf for useful keys
225   while ( searchpath ) {
226     char *k = pnd_box_get_key ( searchpath );
227
228     // does this key look like a category mapping key?
229     if ( strncasecmp ( k, "categories.@", 12 ) == 0 ) {
230       k += 12;
231
232       // iterate across 'words' in v, assigning catmaps to them
233       SEARCHCHUNK_PRE
234       {
235         //pnd_log ( pndn_debug, "target(%s) from(%s)\n", k, buffer );
236
237         category_push ( k, NULL, 0, NULL /* fspath */ );
238         g_catmaps [ g_catmapcount ].target = category_query ( k );
239         g_catmaps [ g_catmapcount ].from = strdup ( buffer );
240         g_catmapcount++;
241
242       }
243       SEARCHCHUNK_POST
244
245     } // if key looks like catmap
246
247     searchpath = pnd_box_get_next ( searchpath );
248   } // while each conf key
249
250   return ( 1 );
251 }
252
253 mm_category_t *category_map_query ( char *cat ) {
254   unsigned char i;
255
256   for ( i = 0; i < g_catmapcount; i++ ) {
257     if ( strcasecmp ( g_catmaps [ i ].from, cat ) == 0 ) {
258       return ( g_catmaps [ i ].target );
259     }
260   }
261
262   return ( NULL );
263 }
264
265 unsigned char category_meta_push ( char *catname, pnd_disco_t *app, pnd_conf_handle ovrh ) {
266   mm_category_t *cat;
267
268   // do we honour cat mapping at all?
269   if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_on", 0 ) ) {
270
271     // is this guy mapped?
272     cat = category_map_query ( catname );
273
274     if ( cat ) {
275       return ( category_push ( cat -> catname, app, ovrh, NULL /* fspath */ ) );
276     }
277
278     // not mapped.. but default?
279     if ( pnd_conf_get_as_int_d ( g_conf, "categories.map_default_on", 0 ) ) {
280       char *def = pnd_conf_get_as_char ( g_conf, "categories.map_default_cat" );
281       if ( def ) {
282         return ( category_push ( def, app, ovrh, NULL /* fspath */ ) );
283       }
284     }
285
286   } // cat map is desired?
287
288   // not default, just do it
289   return ( category_push ( catname, app, ovrh, NULL /* fspath */ ) );
290 }
291
292 unsigned char category_fs_restock ( mm_category_t *cat ) {
293
294   if ( ! cat -> fspath ) {
295     return ( 1 ); // not a filesystem browser tab
296   }
297
298   // clear any existing baggage
299   //
300
301   // apprefs
302   mm_appref_t *iter = cat -> refs, *next;
303   while ( iter ) {
304     next = iter -> next;
305     free ( iter );
306     iter = next;
307   }
308   cat -> refs = NULL;
309
310   // discos
311   if ( cat -> disco ) {
312     pnd_disco_t *p = pnd_box_get_head ( cat -> disco );
313     pnd_disco_t *n;
314     while ( p ) {
315       n = pnd_box_get_next ( p );
316       pnd_disco_destroy ( p );
317       p = n;
318     }
319     pnd_box_delete ( cat -> disco );
320   }
321
322   // rescan the filesystem
323   //
324
325   //pnd_log ( pndn_debug, "Restocking cat %s with path %s\n", cat -> catname, cat -> fspath );
326   DIR *d;
327
328   if ( ( d = opendir ( cat -> fspath ) ) ) {
329     struct dirent *de = readdir ( d );
330
331     pnd_disco_t *disco;
332     char uid [ 100 ];
333
334     cat -> disco = pnd_box_new ( cat -> catname );
335
336     while ( de ) {
337
338       struct stat buffy;
339       char fullpath [ PATH_MAX ];
340       sprintf ( fullpath, "%s/%s", cat -> fspath, de -> d_name );
341       int statret = stat ( fullpath, &buffy );
342
343       // if file is executable somehow or another
344       if ( statret == 0 &&
345            buffy.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)
346          )
347       {
348         // determine unique-id
349         sprintf ( uid, "%d", (int) de -> d_ino );
350         disco = NULL;
351
352         switch ( de -> d_type ) {
353
354         case DT_DIR:
355           if ( strcmp ( de -> d_name, "." ) == 0 ) {
356             // ignore ".", but ".." is fine
357           } else {
358             disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
359             disco -> object_type = pnd_object_type_directory; // suggest to Grid that its a dir
360           }
361           break;
362         case DT_UNKNOWN:
363         case DT_REG:
364           disco = pnd_box_allocinsert ( cat -> disco, uid, sizeof(pnd_disco_t) );
365           disco -> object_type = pnd_object_type_unknown; // suggest to Grid that its a file
366           break;
367
368         } // switch
369
370         // found a directory or executable?
371         if ( disco ) {
372           // register with this category
373           disco -> unique_id = strdup ( uid );
374           disco -> title_en = strdup ( de -> d_name );
375           disco -> object_flags = PND_DISCO_GENERATED;
376           disco -> object_path = strdup ( cat -> fspath );
377           disco -> object_filename = strdup ( de -> d_name );
378           category_push ( cat -> catname, disco, 0, NULL /* fspath already set */ );
379           // if a override icon exists, cache it up
380           cache_icon ( disco, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ),
381                        pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 ) );
382         }
383
384       } // stat
385
386       // next
387       de = readdir ( d );
388     }
389
390     closedir ( d );
391   }
392
393   return ( 1 );
394 }