e1fc0ef09c43cec7453c32582224ec7f2ae0b6c9
[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
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_app_name_en ( pxmlh ) ) {
228         p -> title_en = strdup ( pnd_pxml_get_app_name_en ( pxmlh ) );
229       }
230       if ( pnd_pxml_get_description_en ( pxmlh ) ) {
231         p -> desc_en = strdup ( pnd_pxml_get_description_en ( pxmlh ) );
232       }
233       if ( pnd_pxml_get_icon ( pxmlh ) ) {
234         p -> icon = strdup ( pnd_pxml_get_icon ( pxmlh ) );
235       }
236       if ( pnd_pxml_get_exec ( pxmlh ) ) {
237         p -> exec = strdup ( pnd_pxml_get_exec ( pxmlh ) );
238       }
239       if ( pnd_pxml_get_execargs ( pxmlh ) ) {
240         p -> execargs = strdup ( pnd_pxml_get_execargs ( pxmlh ) );
241       }
242       if ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) ) {
243         p -> option_no_x11 = strdup ( pnd_pxml_get_exec_option_no_x11 ( pxmlh ) );
244       }
245       if ( pnd_pxml_get_unique_id ( pxmlh ) ) {
246         p -> unique_id = strdup ( pnd_pxml_get_unique_id ( pxmlh ) );
247       }
248       if ( pnd_pxml_get_appdata_dirname ( pxmlh ) ) {
249         p -> appdata_dirname = strdup ( pnd_pxml_get_appdata_dirname ( pxmlh ) );
250       }
251       if ( pnd_pxml_get_clockspeed ( pxmlh ) ) {
252         p -> clockspeed = strdup ( pnd_pxml_get_clockspeed ( pxmlh ) ); 
253       }
254       if ( pnd_pxml_get_startdir ( pxmlh ) ) {
255         p -> startdir = strdup ( pnd_pxml_get_startdir ( pxmlh ) ); 
256       }
257       // category kruft
258       if ( pnd_pxml_get_main_category ( pxmlh ) ) {
259         p -> main_category = strdup ( pnd_pxml_get_main_category ( pxmlh ) );
260       }
261       if ( pnd_pxml_get_subcategory1 ( pxmlh ) ) {
262         p -> main_category1 = strdup ( pnd_pxml_get_subcategory1 ( pxmlh ) );
263       }
264       if ( pnd_pxml_get_subcategory2 ( pxmlh ) ) {
265         p -> main_category2 = strdup ( pnd_pxml_get_subcategory2 ( pxmlh ) );
266       }
267       if ( pnd_pxml_get_altcategory ( pxmlh ) ) {
268         p -> alt_category = strdup ( pnd_pxml_get_altcategory ( pxmlh ) );
269       }
270       if ( pnd_pxml_get_altsubcategory1 ( pxmlh ) ) {
271         p -> alt_category1 = strdup ( pnd_pxml_get_altsubcategory1 ( pxmlh ) );
272       }
273       if ( pnd_pxml_get_altsubcategory2 ( pxmlh ) ) {
274         p -> alt_category2 = strdup ( pnd_pxml_get_altsubcategory2 ( pxmlh ) );
275       }
276       // preview pics
277       if ( ( z = pnd_pxml_get_previewpic1 ( pxmlh ) ) ) {
278         p -> preview_pic1 = strdup ( z );
279       }
280       if ( ( z = pnd_pxml_get_previewpic2 ( pxmlh ) ) ) {
281         p -> preview_pic2 = strdup ( z );
282       }
283       // mkdirs
284       if ( pnd_pxml_get_mkdir ( pxmlh ) ) {
285         p -> mkdir_sp = strdup ( pnd_pxml_get_mkdir ( pxmlh ) );
286       }
287       // info
288       if ( pnd_pxml_get_info_src ( pxmlh ) ) {
289         p -> info_filename = strdup ( pnd_pxml_get_info_src ( pxmlh ) );
290       }
291       if ( pnd_pxml_get_info_name ( pxmlh ) ) {
292         p -> info_name = strdup ( pnd_pxml_get_info_name ( pxmlh ) );
293       }
294       if ( pnd_pxml_get_info_type ( pxmlh ) ) {
295         p -> info_type = strdup ( pnd_pxml_get_info_type ( pxmlh ) );
296       }
297
298       // look for any PXML overrides, if requested
299       if ( disco_overrides ) {
300         pnd_pxml_merge_override ( pxmlh, disco_overrides );
301       }
302
303       // handle ovr overrides
304       // try to load a same-path-as-pnd override file
305       if ( ovrh == 0 ) {
306         sprintf ( ovrfile, "%s/%s", p -> object_path, p -> object_filename );
307         fixpxml = strcasestr ( ovrfile, PND_PACKAGE_FILEEXT );
308         if ( fixpxml ) {
309           strcpy ( fixpxml, PXML_SAMEPATH_OVERRIDE_FILEEXT );
310           struct stat statbuf;
311           if ( stat ( ovrfile, &statbuf ) == 0 ) {
312             ovrh = pnd_conf_fetch_by_path ( ovrfile );
313
314             if ( ! ovrh ) {
315               // couldn't pull conf out of file, so don't try again
316               ovrh = (void*)(-1);
317             }
318
319           } else {
320             ovrh = (void*)(-1); // not found, don't try again
321           } // stat
322         } // can find .pnd
323       } // tried ovr yet?
324
325       // is ovr file open?
326       if ( ovrh != 0 && ovrh != (void*)(-1) ) {
327         // pull in appropriate values
328         char key [ 100 ];
329         char *v;
330
331         // set the flag regardless, so its for all subapps
332         p -> object_flags |= PND_DISCO_FLAG_OVR;
333
334         // title
335         snprintf ( key, 100, "Application-%u.title", p -> subapp_number );
336         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
337           if ( p -> title_en ) {
338             free ( p -> title_en );
339           }
340           p -> title_en = strdup ( v );
341         }
342
343         // clockspeed
344         snprintf ( key, 100, "Application-%u.clockspeed", p -> subapp_number );
345         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
346           if ( p -> clockspeed ) {
347             free ( p -> clockspeed );
348           }
349           p -> clockspeed = strdup ( v );
350         }
351
352         // appdata dirname
353         snprintf ( key, 100, "Application-%u.appdata", p -> subapp_number );
354         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
355           if ( p -> appdata_dirname ) {
356             free ( p -> appdata_dirname );
357           }
358           p -> appdata_dirname = strdup ( v );
359         }
360
361         // categories
362         snprintf ( key, 100, "Application-%u.maincategory", p -> subapp_number );
363         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
364           if ( p -> main_category ) {
365             free ( p -> main_category );
366           }
367           p -> main_category = strdup ( v );
368         }
369         snprintf ( key, 100, "Application-%u.maincategorysub1", p -> subapp_number );
370         if ( ( v = pnd_conf_get_as_char ( ovrh, key ) ) ) {
371           if ( p -> main_category1 ) {
372             free ( p -> main_category1 );
373             p -> main_category1 = NULL;
374           }
375           if ( strcasecmp ( v, "NoSubcategory" ) != 0 ) {
376             p -> main_category1 = strdup ( v );
377           }
378         }
379
380       } // got ovr conf loaded?
381
382     } else {
383       //printf ( "Invalid PXML; skipping.\n" );
384     }
385
386     // ditch pxml
387     pnd_pxml_delete ( pxmlh );
388
389   } // while pxmlh is good
390
391   // free up ovr
392   if ( ovrh != 0 && ovrh != (void*)(-1) ) {
393     pnd_box_delete ( ovrh );
394   }
395
396   // free up the applist
397   free ( pxmlapps );
398
399   return ( FTW_CONTINUE ); // continue the tree walk
400 }
401
402 pnd_box_handle pnd_disco_search ( char *searchpath, char *overridespath ) {
403
404   //printf ( "Searchpath to discover: '%s'\n", searchpath );
405
406   // alloc a container for the result set
407   disco_box = pnd_box_new ( "discovery" );
408   disco_overrides = overridespath;
409
410   /* iterate across the paths within the searchpath, attempting to locate applications
411    */
412
413   SEARCHPATH_PRE
414   {
415
416     // invoke the dir walking function; thankfully Linux includes a pretty good one
417     nftw ( buffer,               // path to descend
418            pnd_disco_callback,   // callback to do processing
419            10,                   // no more than X open fd's at once
420            FTW_PHYS | FTW_ACTIONRETVAL );   // do not follow symlinks
421
422   }
423   SEARCHPATH_POST
424
425   // return whatever we found, or NULL if nada
426   if ( ! pnd_box_get_head ( disco_box ) ) {
427     pnd_box_delete ( disco_box );
428     disco_box = NULL;
429   }
430
431   return ( disco_box );
432 }
433
434 pnd_box_handle pnd_disco_file ( char *path, char *filename ) {
435   struct stat statbuf;
436
437   // set up container
438   disco_overrides = NULL;
439   disco_box = pnd_box_new ( "discovery" );
440
441   // path
442   char fullpath [ PATH_MAX ];
443   sprintf ( fullpath, "%s/%s", path, filename );
444
445   // fake it
446   if ( stat ( fullpath, &statbuf ) < 0 ) {
447     return ( 0 );
448   }
449
450   struct FTW ftw;
451   ftw.base = strlen ( path );
452   ftw.level = 0;
453
454   pnd_disco_callback ( fullpath, &statbuf, FTW_F, &ftw );
455
456   // return whatever we found, or NULL if nada
457   if ( ! pnd_box_get_head ( disco_box ) ) {
458     pnd_box_delete ( disco_box );
459     disco_box = NULL;
460   }
461
462   return ( disco_box );
463 }