No really, a workaround.
[pandora-libraries.git] / lib / pnd_discovery.c
1
2 #include <stdio.h> /* for FILE etc */
3 #include <stdlib.h> /* for malloc */
4 #include <unistd.h> /* for unlink */
5 #include <limits.h> /* for PATH_MAX */
6 #include <sys/types.h>
7 #include <sys/stat.h>
8
9 #define __USE_GNU /* for strcasestr */
10 #include <string.h> /* for making ftw.h happy */
11
12 #define _XOPEN_SOURCE 500
13 #define __USE_XOPEN_EXTENDED
14 #include <ftw.h> /* for nftw, tree walker */
15
16 #include "pnd_container.h"
17 #include "pnd_pxml.h"
18 #include "pnd_discovery.h"
19 #include "pnd_pathiter.h"
20 #include "pnd_apps.h"
21 #include "pnd_pndfiles.h"
22 #include "pnd_logger.h"
23 #include "pnd_conf.h"
24
25 // need these 'globals' due to the way nftw and ftw work :/
26 static pnd_box_handle disco_box;
27 static char *disco_overrides = NULL;
28
29 void pnd_disco_destroy ( pnd_disco_t *p ) {
30
31   if ( p -> title_en ) {       free ( p -> title_en );    }
32   if ( p -> unique_id ) {      free ( p -> unique_id );   }
33   if ( p -> appdata_dirname ) { free ( p -> appdata_dirname );   }
34   if ( p -> icon )     {       free ( p -> icon );        }
35   if ( p -> exec )     {       free ( p -> exec );        }
36   if ( p -> execargs ) {       free ( p -> execargs );    }
37   if ( p -> clockspeed ) {     free ( p -> clockspeed );  }
38   if ( p -> startdir ) {       free ( p -> startdir );    }
39   if ( p -> option_no_x11 ) {  free ( p -> option_no_x11 );  }
40   if ( p -> main_category ) {  free ( p -> main_category );  }
41   if ( p -> main_category1 ) { free ( p -> main_category1 ); }
42   if ( p -> main_category2 ) { free ( p -> main_category2 ); }
43   if ( p -> alt_category ) {   free ( p -> alt_category );   }
44   if ( p -> alt_category1 ) {  free ( p -> alt_category1 );  }
45   if ( p -> alt_category2 ) {  free ( p -> alt_category2 );  }
46   if ( p -> mkdir_sp )      {  free ( p -> mkdir_sp );       }
47   if ( p -> info_name )     {  free ( p -> info_name );       }
48   if ( p -> info_type )     {  free ( p -> info_type );       }
49   if ( p -> info_filename ) {  free ( p -> info_filename );       }
50
51   return;
52 }
53
54 static int pnd_disco_callback ( const char *fpath, const struct stat *sb,
55                                 int typeflag, struct FTW *ftwbuf )
56 {
57   unsigned char valid = pnd_object_type_unknown;
58   pnd_pxml_handle pxmlh = 0;
59   pnd_pxml_handle *pxmlapps = NULL;
60   pnd_pxml_handle *pxmlappiter;
61   unsigned int pxml_close_pos = 0;
62   unsigned char logit = pnd_log_do_buried_logging();
63
64   if ( logit ) {
65     pnd_log ( PND_LOG_DEFAULT, "disco callback encountered '%s'\n", fpath );
66   }
67
68   // PXML.xml is a possible application candidate (and not a dir named PXML.xml :)
69   if ( typeflag & FTW_D ) {
70     if ( logit ) {
71       pnd_log ( PND_LOG_DEFAULT, " .. is dir, skipping\n" );
72     }
73     return ( 0 ); // skip directories and other non-regular files
74   }
75
76   // PND/PNZ file and others may be valid as well .. but lets leave that for now
77   //   printf ( "%s %s\n", fpath + ftwbuf -> base, PND_PACKAGE_FILEEXT );
78   if ( strcasecmp ( fpath + ftwbuf -> base, PXML_FILENAME ) == 0 ) {
79     valid = pnd_object_type_directory;
80   } else if ( strcasestr ( fpath + ftwbuf -> base, PND_PACKAGE_FILEEXT "\0" ) ) {
81     valid = pnd_object_type_pnd;
82   }
83
84   // if not a file of interest, just keep looking until we run out
85   if ( ! valid ) {
86     if ( logit ) {
87       pnd_log ( PND_LOG_DEFAULT, " .. bad filename, skipping\n" );
88     }
89     return ( 0 );
90   }
91
92   // potentially a valid application
93   if ( valid == pnd_object_type_directory ) {
94     // Plaintext PXML file
95     //printf ( "PXML: disco callback encountered '%s'\n", fpath );
96
97     // pick up the PXML if we can
98     pxmlapps = pnd_pxml_fetch ( (char*) fpath );
99
100   } else if ( valid == pnd_object_type_pnd ) {
101     // PND ... ??
102     FILE *f;
103     char pxmlbuf [ 32 * 1024 ]; // TBD: assuming 32k pxml accrual buffer is a little lame
104
105     //printf ( "PND: disco callback encountered '%s'\n", fpath );
106
107     // is this a valid .pnd file? The filename is a candidate already, but verify..
108     // .. presence of PXML appended, or at least contained within?
109     // .. presence of an icon appended after PXML?
110
111     // open it up..
112     f = fopen ( fpath, "r" );
113
114     // try to locate the PXML portion
115     if ( ! pnd_pnd_seek_pxml ( f ) ) {
116       fclose ( f );
117       return ( 0 ); // pnd or not, but not to spec. Pwn'd the pnd?
118     }
119
120     // accrue it into a buffer
121     if ( ! pnd_pnd_accrue_pxml ( f, pxmlbuf, 32 * 1024 ) ) {
122       fclose ( f );
123       return ( 0 );
124     }
125
126     //printf ( "buffer is %s\n", pxmlbuf );
127     //fflush ( stdout );
128
129 #if 1 // icon
130     // for convenience, lets skip along past trailing newlines/CR's in hopes of finding icon data?
131     {
132       unsigned int pos = ftell ( f );
133       char pngbuffer [ 16 ]; // \211 P N G \r \n \032 \n
134       pngbuffer [ 0 ] = 137;      pngbuffer [ 1 ] = 80;      pngbuffer [ 2 ] = 78;      pngbuffer [ 3 ] = 71;
135       pngbuffer [ 4 ] = 13;       pngbuffer [ 5 ] = 10;       pngbuffer [ 6 ] = 26;      pngbuffer [ 7 ] = 10;
136
137       unsigned char padtests = 20;
138       unsigned int padstart = ftell ( f );
139
140       // seek back 10 (should be back into the /PXML> part) to catch any appending-icon-no-line-endings funny business
141       fseek ( f, -10, SEEK_CUR );
142
143       while ( padtests ) {
144
145         if ( fread ( pngbuffer + 8, 8, 1, f ) == 1 ) {
146           if ( memcmp ( pngbuffer, pngbuffer + 8, 8 ) == 0 ) {
147             pxml_close_pos = ftell ( f ) - 8;
148             break;
149           } // if
150           fseek ( f, -7, SEEK_CUR ); // seek back 7 (so we're 1 further than we started, since PNG header is 8b)
151         } // if read
152
153         padtests --;
154       } // while
155
156       if ( ! padtests ) {
157         // no icon found, so back to where we started looking
158         fseek ( f, padstart, SEEK_SET );
159       }
160
161     } // icon
162 #endif
163
164     // by now, we have <PXML> .. </PXML>, try to parse..
165     pxmlapps = pnd_pxml_fetch_buffer ( (char*) fpath, pxmlbuf );
166
167     // done with file
168     fclose ( f );
169
170   }
171
172   // pxmlh is useful?
173   if ( ! pxmlapps ) {
174     return ( 0 ); // continue tree walk
175   }
176
177   // for ovr-file
178   char ovrfile [ PATH_MAX ];
179   pnd_box_handle ovrh = 0; // 0 didn't try, -1 tried and failed, >0 tried and got
180
181   // iterate across apps in the PXML
182   pxmlappiter = pxmlapps;
183   while ( 1 ) {
184     pxmlh = *pxmlappiter;
185     pxmlappiter++;
186
187     if ( ! pxmlh ) {
188       break; // all done
189     }
190
191     // check for validity and add to resultset if it looks executable
192     if ( pnd_is_pxml_valid_app ( pxmlh ) ) {
193       pnd_disco_t *p;
194       char *fixpxml;
195       char *z;
196
197       //pnd_log ( PND_LOG_DEFAULT, "Setting up discovered app %u\n", ((pnd_pxml_t*) pxmlh) -> subapp_number );
198
199       p = pnd_box_allocinsert ( disco_box, (char*) fpath, sizeof(pnd_disco_t) );
200
201       // base paths
202       p -> object_path = strdup ( fpath );
203
204       if ( ( fixpxml = strcasestr ( p -> object_path, PXML_FILENAME ) ) ) {
205         *fixpxml = '\0'; // if this is not a .pnd, lop off the PXML.xml at the end
206       } else if ( ( fixpxml = strrchr ( p -> object_path, '/' ) ) ) {
207         *(fixpxml+1) = '\0'; // for pnd, lop off to last /
208       }
209
210       if ( ( fixpxml = strrchr ( fpath, '/' ) ) ) {
211         p -> object_filename = strdup ( fixpxml + 1 );
212       }
213
214       // subapp-number
215       p -> subapp_number = ((pnd_pxml_t*) pxmlh) -> subapp_number;
216
217       // png icon path
218       p -> pnd_icon_pos = pxml_close_pos;
219
220       // type
221       p -> object_type = valid;
222
223       // PXML fields
224       if ( pnd_pxml_get_app_name_en ( pxmlh ) ) {
225         p -> title_en = strdup ( pnd_pxml_get_app_name_en ( pxmlh ) );
226       }
227       if ( pnd_pxml_get_description_en ( pxmlh ) ) {
228         p -> desc_en = strdup ( pnd_pxml_get_description_en ( pxmlh ) );
229       }
230       if ( pnd_pxml_get_icon ( pxmlh ) ) {
231         p -> icon = strdup ( pnd_pxml_get_icon ( pxmlh ) );
232       }
233       if ( pnd_pxml_get_exec ( pxmlh ) ) {
234         p -> exec = strdup ( pnd_pxml_get_exec ( pxmlh ) );
235       }
236       if ( pnd_pxml_get_execargs ( pxmlh ) ) {
237         p -> execargs = strdup ( pnd_pxml_get_execargs ( pxmlh ) );
238       }
239       if ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) ) {
240         p -> option_no_x11 = strdup ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) );
241       }
242       if ( pnd_pxml_get_unique_id ( pxmlh ) ) {
243         p -> unique_id = strdup ( pnd_pxml_get_unique_id ( pxmlh ) );
244       }
245       if ( pnd_pxml_get_appdata_dirname ( pxmlh ) ) {
246         p -> appdata_dirname = strdup ( pnd_pxml_get_appdata_dirname ( pxmlh ) );
247       }
248       if ( pnd_pxml_get_clockspeed ( pxmlh ) ) {
249         p -> clockspeed = strdup ( pnd_pxml_get_clockspeed ( pxmlh ) ); 
250       }
251       if ( pnd_pxml_get_startdir ( pxmlh ) ) {
252         p -> startdir = strdup ( pnd_pxml_get_startdir ( pxmlh ) ); 
253       }
254       // category kruft
255       if ( pnd_pxml_get_main_category ( pxmlh ) ) {
256         p -> main_category = strdup ( pnd_pxml_get_main_category ( pxmlh ) );
257       }
258       if ( pnd_pxml_get_subcategory1 ( pxmlh ) ) {
259         p -> main_category1 = strdup ( pnd_pxml_get_subcategory1 ( pxmlh ) );
260       }
261       if ( pnd_pxml_get_subcategory2 ( pxmlh ) ) {
262         p -> main_category2 = strdup ( pnd_pxml_get_subcategory2 ( pxmlh ) );
263       }
264       if ( pnd_pxml_get_altcategory ( pxmlh ) ) {
265         p -> alt_category = strdup ( pnd_pxml_get_altcategory ( pxmlh ) );
266       }
267       if ( pnd_pxml_get_altsubcategory1 ( pxmlh ) ) {
268         p -> alt_category1 = strdup ( pnd_pxml_get_altsubcategory1 ( pxmlh ) );
269       }
270       if ( pnd_pxml_get_altsubcategory2 ( pxmlh ) ) {
271         p -> alt_category2 = strdup ( pnd_pxml_get_altsubcategory2 ( pxmlh ) );
272       }
273       // preview pics
274       if ( ( z = pnd_pxml_get_previewpic1 ( pxmlh ) ) ) {
275         p -> preview_pic1 = strdup ( z );
276       }
277       if ( ( z = pnd_pxml_get_previewpic2 ( pxmlh ) ) ) {
278         p -> preview_pic2 = strdup ( z );
279       }
280       // mkdirs
281       if ( pnd_pxml_get_mkdir ( pxmlh ) ) {
282         p -> mkdir_sp = strdup ( pnd_pxml_get_mkdir ( pxmlh ) );
283       }
284       // info
285       if ( pnd_pxml_get_info_src ( pxmlh ) ) {
286         p -> info_filename = strdup ( pnd_pxml_get_info_src ( pxmlh ) );
287       }
288       if ( pnd_pxml_get_info_name ( pxmlh ) ) {
289         p -> info_name = strdup ( pnd_pxml_get_info_name ( pxmlh ) );
290       }
291       if ( pnd_pxml_get_info_type ( pxmlh ) ) {
292         p -> info_type = strdup ( pnd_pxml_get_info_type ( pxmlh ) );
293       }
294
295       // look for any PXML overrides, if requested
296       if ( disco_overrides ) {
297         pnd_pxml_merge_override ( pxmlh, disco_overrides );
298       }
299
300       // handle ovr overrides
301       // try to load a same-path-as-pnd override file
302       if ( ovrh == 0 ) {
303         sprintf ( ovrfile, "%s/%s", p -> object_path, p -> object_filename );
304         fixpxml = strcasestr ( ovrfile, PND_PACKAGE_FILEEXT );
305         if ( fixpxml ) {
306           strcpy ( fixpxml, PXML_SAMEPATH_OVERRIDE_FILEEXT );
307           struct stat statbuf;
308           if ( stat ( ovrfile, &statbuf ) == 0 ) {
309             ovrh = pnd_conf_fetch_by_path ( ovrfile );
310
311             if ( ! ovrh ) {
312               // couldn't pull conf out of file, so don't try again
313               ovrh = (void*)(-1);
314             }
315
316           } else {
317             ovrh = (void*)(-1); // not found, don't try again
318           } // stat
319         } // can find .pnd
320       } // tried ovr yet?
321
322       // is ovr file open?
323       if ( ovrh != 0 && ovrh != (void*)(-1) ) {
324         // pull in appropriate values
325         char key [ 100 ];
326         char *v;
327
328         // set the flag regardless, so its for all subapps
329         p -> object_flags |= PND_DISCO_FLAG_OVR;
330
331         // title
332         snprintf ( key, 100, "Application-%u.title", p -> subapp_number );
333         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
334           if ( p -> title_en ) {
335             free ( p -> title_en );
336           }
337           p -> title_en = strdup ( v );
338         }
339
340         // clockspeed
341         snprintf ( key, 100, "Application-%u.clockspeed", p -> subapp_number );
342         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
343           if ( p -> clockspeed ) {
344             free ( p -> clockspeed );
345           }
346           p -> clockspeed = strdup ( v );
347         }
348
349         // appdata dirname
350         snprintf ( key, 100, "Application-%u.appdata", p -> subapp_number );
351         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
352           if ( p -> appdata_dirname ) {
353             free ( p -> appdata_dirname );
354           }
355           p -> appdata_dirname = strdup ( v );
356         }
357
358         // categories
359         snprintf ( key, 100, "Application-%u.maincategory", p -> subapp_number );
360         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
361           if ( p -> main_category ) {
362             free ( p -> main_category );
363           }
364           p -> main_category = strdup ( v );
365         }
366         snprintf ( key, 100, "Application-%u.maincategorysub1", p -> subapp_number );
367         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
368           if ( p -> main_category1 ) {
369             free ( p -> main_category1 );
370           }
371           p -> main_category1 = strdup ( v );
372         }
373
374       } // got ovr conf loaded?
375
376     } else {
377       //printf ( "Invalid PXML; skipping.\n" );
378     }
379
380     // ditch pxml
381     pnd_pxml_delete ( pxmlh );
382
383   } // while pxmlh is good
384
385   // free up ovr
386   if ( ovrh != 0 && ovrh != (void*)(-1) ) {
387     pnd_box_delete ( ovrh );
388   }
389
390   // free up the applist
391   free ( pxmlapps );
392
393   return ( 0 ); // continue the tree walk
394 }
395
396 pnd_box_handle pnd_disco_search ( char *searchpath, char *overridespath ) {
397
398   //printf ( "Searchpath to discover: '%s'\n", searchpath );
399
400   // alloc a container for the result set
401   disco_box = pnd_box_new ( "discovery" );
402   disco_overrides = overridespath;
403
404   /* iterate across the paths within the searchpath, attempting to locate applications
405    */
406
407   SEARCHPATH_PRE
408   {
409
410     // invoke the dir walking function; thankfully Linux includes a pretty good one
411     nftw ( buffer,               // path to descend
412            pnd_disco_callback,   // callback to do processing
413            10,                   // no more than X open fd's at once
414            FTW_PHYS );           // do not follow symlinks
415
416   }
417   SEARCHPATH_POST
418
419   // return whatever we found, or NULL if nada
420   if ( ! pnd_box_get_head ( disco_box ) ) {
421     pnd_box_delete ( disco_box );
422     disco_box = NULL;
423   }
424
425   return ( disco_box );
426 }
427
428 pnd_box_handle pnd_disco_file ( char *path, char *filename ) {
429   struct stat statbuf;
430
431   // set up container
432   disco_overrides = NULL;
433   disco_box = pnd_box_new ( "discovery" );
434
435   // path
436   char fullpath [ PATH_MAX ];
437   sprintf ( fullpath, "%s/%s", path, filename );
438
439   // fake it
440   if ( stat ( fullpath, &statbuf ) < 0 ) {
441     return ( 0 );
442   }
443
444   struct FTW ftw;
445   ftw.base = strlen ( path );
446   ftw.level = 0;
447
448   pnd_disco_callback ( fullpath, &statbuf, FTW_F, &ftw );
449
450   // return whatever we found, or NULL if nada
451   if ( ! pnd_box_get_head ( disco_box ) ) {
452     pnd_box_delete ( disco_box );
453     disco_box = NULL;
454   }
455
456   return ( disco_box );
457 }