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