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