8afbb389401b6959c99fc95147e5cadca340e5c4
[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       char pngbuffer [ 16 ]; // \211 P N G \r \n \032 \n
133       pngbuffer [ 0 ] = 137;      pngbuffer [ 1 ] = 80;      pngbuffer [ 2 ] = 78;      pngbuffer [ 3 ] = 71;
134       pngbuffer [ 4 ] = 13;       pngbuffer [ 5 ] = 10;       pngbuffer [ 6 ] = 26;      pngbuffer [ 7 ] = 10;
135
136       unsigned char padtests = 20;
137       unsigned int padstart = ftell ( f );
138
139       // seek back 10 (should be back into the /PXML> part) to catch any appending-icon-no-line-endings funny business
140       fseek ( f, -10, SEEK_CUR );
141
142       while ( padtests ) {
143
144         if ( fread ( pngbuffer + 8, 8, 1, f ) == 1 ) {
145           if ( memcmp ( pngbuffer, pngbuffer + 8, 8 ) == 0 ) {
146             pxml_close_pos = ftell ( f ) - 8;
147             break;
148           } // if
149           fseek ( f, -7, SEEK_CUR ); // seek back 7 (so we're 1 further than we started, since PNG header is 8b)
150         } // if read
151
152         padtests --;
153       } // while
154
155       if ( ! padtests ) {
156         // no icon found, so back to where we started looking
157         fseek ( f, padstart, SEEK_SET );
158       }
159
160     } // icon
161 #endif
162
163     // by now, we have <PXML> .. </PXML>, try to parse..
164     pxmlapps = pnd_pxml_fetch_buffer ( (char*) fpath, pxmlbuf );
165
166     // done with file
167     fclose ( f );
168
169   }
170
171   // pxmlh is useful?
172   if ( ! pxmlapps ) {
173     return ( 0 ); // continue tree walk
174   }
175
176   // for ovr-file
177   char ovrfile [ PATH_MAX ];
178   pnd_box_handle ovrh = 0; // 0 didn't try, -1 tried and failed, >0 tried and got
179
180   // iterate across apps in the PXML
181   pxmlappiter = pxmlapps;
182   while ( 1 ) {
183     pxmlh = *pxmlappiter;
184     pxmlappiter++;
185
186     if ( ! pxmlh ) {
187       break; // all done
188     }
189
190     // check for validity and add to resultset if it looks executable
191     if ( pnd_is_pxml_valid_app ( pxmlh ) ) {
192       pnd_disco_t *p;
193       char *fixpxml;
194       char *z;
195
196       //pnd_log ( PND_LOG_DEFAULT, "Setting up discovered app %u\n", ((pnd_pxml_t*) pxmlh) -> subapp_number );
197
198       p = pnd_box_allocinsert ( disco_box, (char*) fpath, sizeof(pnd_disco_t) );
199
200       // base paths
201       p -> object_path = strdup ( fpath );
202
203       if ( ( fixpxml = strcasestr ( p -> object_path, PXML_FILENAME ) ) ) {
204         *fixpxml = '\0'; // if this is not a .pnd, lop off the PXML.xml at the end
205       } else if ( ( fixpxml = strrchr ( p -> object_path, '/' ) ) ) {
206         *(fixpxml+1) = '\0'; // for pnd, lop off to last /
207       }
208
209       if ( ( fixpxml = strrchr ( fpath, '/' ) ) ) {
210         p -> object_filename = strdup ( fixpxml + 1 );
211       }
212
213       // subapp-number
214       p -> subapp_number = ((pnd_pxml_t*) pxmlh) -> subapp_number;
215
216       // png icon path
217       p -> pnd_icon_pos = pxml_close_pos;
218
219       // type
220       p -> object_type = valid;
221
222       // PXML fields
223       if ( pnd_pxml_get_app_name_en ( pxmlh ) ) {
224         p -> title_en = strdup ( pnd_pxml_get_app_name_en ( pxmlh ) );
225       }
226       if ( pnd_pxml_get_description_en ( pxmlh ) ) {
227         p -> desc_en = strdup ( pnd_pxml_get_description_en ( pxmlh ) );
228       }
229       if ( pnd_pxml_get_icon ( pxmlh ) ) {
230         p -> icon = strdup ( pnd_pxml_get_icon ( pxmlh ) );
231       }
232       if ( pnd_pxml_get_exec ( pxmlh ) ) {
233         p -> exec = strdup ( pnd_pxml_get_exec ( pxmlh ) );
234       }
235       if ( pnd_pxml_get_execargs ( pxmlh ) ) {
236         p -> execargs = strdup ( pnd_pxml_get_execargs ( pxmlh ) );
237       }
238       if ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) ) {
239         p -> option_no_x11 = strdup ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) );
240       }
241       if ( pnd_pxml_get_unique_id ( pxmlh ) ) {
242         p -> unique_id = strdup ( pnd_pxml_get_unique_id ( pxmlh ) );
243       }
244       if ( pnd_pxml_get_appdata_dirname ( pxmlh ) ) {
245         p -> appdata_dirname = strdup ( pnd_pxml_get_appdata_dirname ( pxmlh ) );
246       }
247       if ( pnd_pxml_get_clockspeed ( pxmlh ) ) {
248         p -> clockspeed = strdup ( pnd_pxml_get_clockspeed ( pxmlh ) ); 
249       }
250       if ( pnd_pxml_get_startdir ( pxmlh ) ) {
251         p -> startdir = strdup ( pnd_pxml_get_startdir ( pxmlh ) ); 
252       }
253       // category kruft
254       if ( pnd_pxml_get_main_category ( pxmlh ) ) {
255         p -> main_category = strdup ( pnd_pxml_get_main_category ( pxmlh ) );
256       }
257       if ( pnd_pxml_get_subcategory1 ( pxmlh ) ) {
258         p -> main_category1 = strdup ( pnd_pxml_get_subcategory1 ( pxmlh ) );
259       }
260       if ( pnd_pxml_get_subcategory2 ( pxmlh ) ) {
261         p -> main_category2 = strdup ( pnd_pxml_get_subcategory2 ( pxmlh ) );
262       }
263       if ( pnd_pxml_get_altcategory ( pxmlh ) ) {
264         p -> alt_category = strdup ( pnd_pxml_get_altcategory ( pxmlh ) );
265       }
266       if ( pnd_pxml_get_altsubcategory1 ( pxmlh ) ) {
267         p -> alt_category1 = strdup ( pnd_pxml_get_altsubcategory1 ( pxmlh ) );
268       }
269       if ( pnd_pxml_get_altsubcategory2 ( pxmlh ) ) {
270         p -> alt_category2 = strdup ( pnd_pxml_get_altsubcategory2 ( pxmlh ) );
271       }
272       // preview pics
273       if ( ( z = pnd_pxml_get_previewpic1 ( pxmlh ) ) ) {
274         p -> preview_pic1 = strdup ( z );
275       }
276       if ( ( z = pnd_pxml_get_previewpic2 ( pxmlh ) ) ) {
277         p -> preview_pic2 = strdup ( z );
278       }
279       // mkdirs
280       if ( pnd_pxml_get_mkdir ( pxmlh ) ) {
281         p -> mkdir_sp = strdup ( pnd_pxml_get_mkdir ( pxmlh ) );
282       }
283       // info
284       if ( pnd_pxml_get_info_src ( pxmlh ) ) {
285         p -> info_filename = strdup ( pnd_pxml_get_info_src ( pxmlh ) );
286       }
287       if ( pnd_pxml_get_info_name ( pxmlh ) ) {
288         p -> info_name = strdup ( pnd_pxml_get_info_name ( pxmlh ) );
289       }
290       if ( pnd_pxml_get_info_type ( pxmlh ) ) {
291         p -> info_type = strdup ( pnd_pxml_get_info_type ( pxmlh ) );
292       }
293
294       // look for any PXML overrides, if requested
295       if ( disco_overrides ) {
296         pnd_pxml_merge_override ( pxmlh, disco_overrides );
297       }
298
299       // handle ovr overrides
300       // try to load a same-path-as-pnd override file
301       if ( ovrh == 0 ) {
302         sprintf ( ovrfile, "%s/%s", p -> object_path, p -> object_filename );
303         fixpxml = strcasestr ( ovrfile, PND_PACKAGE_FILEEXT );
304         if ( fixpxml ) {
305           strcpy ( fixpxml, PXML_SAMEPATH_OVERRIDE_FILEEXT );
306           struct stat statbuf;
307           if ( stat ( ovrfile, &statbuf ) == 0 ) {
308             ovrh = pnd_conf_fetch_by_path ( ovrfile );
309
310             if ( ! ovrh ) {
311               // couldn't pull conf out of file, so don't try again
312               ovrh = (void*)(-1);
313             }
314
315           } else {
316             ovrh = (void*)(-1); // not found, don't try again
317           } // stat
318         } // can find .pnd
319       } // tried ovr yet?
320
321       // is ovr file open?
322       if ( ovrh != 0 && ovrh != (void*)(-1) ) {
323         // pull in appropriate values
324         char key [ 100 ];
325         char *v;
326
327         // set the flag regardless, so its for all subapps
328         p -> object_flags |= PND_DISCO_FLAG_OVR;
329
330         // title
331         snprintf ( key, 100, "Application-%u.title", p -> subapp_number );
332         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
333           if ( p -> title_en ) {
334             free ( p -> title_en );
335           }
336           p -> title_en = strdup ( v );
337         }
338
339         // clockspeed
340         snprintf ( key, 100, "Application-%u.clockspeed", p -> subapp_number );
341         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
342           if ( p -> clockspeed ) {
343             free ( p -> clockspeed );
344           }
345           p -> clockspeed = strdup ( v );
346         }
347
348         // appdata dirname
349         snprintf ( key, 100, "Application-%u.appdata", p -> subapp_number );
350         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
351           if ( p -> appdata_dirname ) {
352             free ( p -> appdata_dirname );
353           }
354           p -> appdata_dirname = strdup ( v );
355         }
356
357         // categories
358         snprintf ( key, 100, "Application-%u.maincategory", p -> subapp_number );
359         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
360           if ( p -> main_category ) {
361             free ( p -> main_category );
362           }
363           p -> main_category = strdup ( v );
364         }
365         snprintf ( key, 100, "Application-%u.maincategorysub1", p -> subapp_number );
366         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
367           if ( p -> main_category1 ) {
368             free ( p -> main_category1 );
369             p -> main_category1 = NULL;
370           }
371           if ( strcasecmp ( v, "NoSubcategory" ) != 0 ) {
372             p -> main_category1 = strdup ( v );
373           }
374         }
375
376       } // got ovr conf loaded?
377
378     } else {
379       //printf ( "Invalid PXML; skipping.\n" );
380     }
381
382     // ditch pxml
383     pnd_pxml_delete ( pxmlh );
384
385   } // while pxmlh is good
386
387   // free up ovr
388   if ( ovrh != 0 && ovrh != (void*)(-1) ) {
389     pnd_box_delete ( ovrh );
390   }
391
392   // free up the applist
393   free ( pxmlapps );
394
395   return ( 0 ); // continue the tree walk
396 }
397
398 pnd_box_handle pnd_disco_search ( char *searchpath, char *overridespath ) {
399
400   //printf ( "Searchpath to discover: '%s'\n", searchpath );
401
402   // alloc a container for the result set
403   disco_box = pnd_box_new ( "discovery" );
404   disco_overrides = overridespath;
405
406   /* iterate across the paths within the searchpath, attempting to locate applications
407    */
408
409   SEARCHPATH_PRE
410   {
411
412     // invoke the dir walking function; thankfully Linux includes a pretty good one
413     nftw ( buffer,               // path to descend
414            pnd_disco_callback,   // callback to do processing
415            10,                   // no more than X open fd's at once
416            FTW_PHYS );           // do not follow symlinks
417
418   }
419   SEARCHPATH_POST
420
421   // return whatever we found, or NULL if nada
422   if ( ! pnd_box_get_head ( disco_box ) ) {
423     pnd_box_delete ( disco_box );
424     disco_box = NULL;
425   }
426
427   return ( disco_box );
428 }
429
430 pnd_box_handle pnd_disco_file ( char *path, char *filename ) {
431   struct stat statbuf;
432
433   // set up container
434   disco_overrides = NULL;
435   disco_box = pnd_box_new ( "discovery" );
436
437   // path
438   char fullpath [ PATH_MAX ];
439   sprintf ( fullpath, "%s/%s", path, filename );
440
441   // fake it
442   if ( stat ( fullpath, &statbuf ) < 0 ) {
443     return ( 0 );
444   }
445
446   struct FTW ftw;
447   ftw.base = strlen ( path );
448   ftw.level = 0;
449
450   pnd_disco_callback ( fullpath, &statbuf, FTW_F, &ftw );
451
452   // return whatever we found, or NULL if nada
453   if ( ! pnd_box_get_head ( disco_box ) ) {
454     pnd_box_delete ( disco_box );
455     disco_box = NULL;
456   }
457
458   return ( disco_box );
459 }