Added basic icon support
[pandora-libraries.git] / lib / pnd_discovery.c
1
2 #include <stdio.h> /* for FILE etc */
3 #include <stdlib.h> /* for malloc */
4
5 #define __USE_GNU /* for strcasestr */
6 #include <string.h> /* for making ftw.h happy */
7
8 #define _XOPEN_SOURCE 500
9 #define __USE_XOPEN_EXTENDED
10 #include <ftw.h> /* for nftw, tree walker */
11
12 #include "pnd_container.h"
13 #include "pnd_pxml.h"
14 #include "pnd_discovery.h"
15 #include "pnd_pathiter.h"
16 #include "pnd_apps.h"
17 #include "pnd_pndfiles.h"
18
19 // need these 'globals' due to the way nftw and ftw work :/
20 static pnd_box_handle disco_box;
21 static char *disco_overrides = NULL;
22
23 void pnd_disco_destroy ( pnd_disco_t *p ) {
24
25   if ( p -> title_en ) {
26     free ( p -> title_en );
27   }
28
29   if ( p -> icon ) {
30     free ( p -> icon );
31   }
32
33   if ( p -> exec ) {
34     free ( p -> exec );
35   }
36
37   if ( p -> unique_id ) {
38     free ( p -> unique_id );
39   }
40
41   if ( p -> main_category ) {
42     free ( p -> main_category );
43   }
44
45   if ( p -> clockspeed ) {
46     free ( p -> clockspeed );
47   }
48
49   return;
50 }
51
52 static int pnd_disco_callback ( const char *fpath, const struct stat *sb,
53                                 int typeflag, struct FTW *ftwbuf )
54 {
55   unsigned char valid = pnd_object_type_unknown;
56   pnd_pxml_handle pxmlh = 0;
57   unsigned int pxml_close_pos = 0;
58
59   //printf ( "disco root callback encountered '%s'\n", fpath );
60
61   // PXML.xml is a possible application candidate (and not a dir named PXML.xml :)
62   if ( typeflag & FTW_D ) {
63     //printf ( " .. is dir, skipping\n" );
64     return ( 0 ); // skip directories and other non-regular files
65   }
66
67   // PND/PNZ file and others may be valid as well .. but lets leave that for now
68   //   printf ( "%s %s\n", fpath + ftwbuf -> base, PND_PACKAGE_FILEEXT );
69   if ( strcasecmp ( fpath + ftwbuf -> base, PXML_FILENAME ) == 0 ) {
70     valid = pnd_object_type_directory;
71   } else if ( strcasestr ( fpath + ftwbuf -> base, PND_PACKAGE_FILEEXT "\0" ) ) {
72     valid = pnd_object_type_pnd;
73   }
74
75   // if not a file of interest, just keep looking until we run out
76   if ( ! valid ) {
77     //printf ( " .. bad filename, skipping\n" );
78     return ( 0 );
79   }
80
81   // potentially a valid application
82   if ( valid == pnd_object_type_directory ) {
83     // Plaintext PXML file
84     //printf ( "PXML: disco callback encountered '%s'\n", fpath );
85
86     // pick up the PXML if we can
87     pxmlh = pnd_pxml_fetch ( (char*) fpath );
88
89   } else if ( valid == pnd_object_type_pnd ) {
90     // PND ... ??
91     FILE *f;
92     char pxmlbuf [ 32 * 1024 ]; // TBD: assuming 32k pxml accrual buffer is a little lame
93
94     //printf ( "PND: disco callback encountered '%s'\n", fpath );
95
96     // is this a valid .pnd file? The filename is a candidate already, but verify..
97     // .. presence of PXML appended, or at least contained within?
98     // .. presence of an icon appended after PXML?
99
100     // open it up..
101     f = fopen ( fpath, "r" );
102
103     // try to locate the PXML portion
104     if ( ! pnd_pnd_seek_pxml ( f ) ) {
105       fclose ( f );
106       return ( 0 ); // pnd or not, but not to spec. Pwn'd the pnd?
107     }
108
109     // accrue it into a buffer
110     if ( ! pnd_pnd_accrue_pxml ( f, pxmlbuf, 32 * 1024 ) ) {
111       fclose ( f );
112       return ( 0 );
113     }
114
115     //printf ( "buffer is %s\n", pxmlbuf );
116     //fflush ( stdout );
117
118 #if 1 // icon
119     // for convenience, lets skip along past trailing newlines/CR's in hopes of finding icon data?
120     {
121       unsigned int pos = ftell ( f );
122       char pngbuffer [ 16 ]; // \211 P N G \r \n \032 \n
123       pngbuffer [ 0 ] = 137;      pngbuffer [ 1 ] = 80;      pngbuffer [ 2 ] = 78;      pngbuffer [ 3 ] = 71;
124       pngbuffer [ 4 ] = 13;       pngbuffer [ 5 ] = 10;       pngbuffer [ 6 ] = 26;      pngbuffer [ 7 ] = 10;
125       if ( fread ( pngbuffer + 8, 8, 1, f ) == 1 ) {
126         if ( memcmp ( pngbuffer, pngbuffer + 8, 8 ) == 0 ) {
127           pxml_close_pos = pos;
128         }
129       }
130     } // icon
131 #endif
132
133     // by now, we have <PXML> .. </PXML>, try to parse..
134     pxmlh = pnd_pxml_fetch_buffer ( (char*) fpath, pxmlbuf );
135
136     // done with file
137     fclose ( f );
138
139   }
140
141   // pxmlh is useful?
142   if ( pxmlh ) {
143
144     // look for any overrides, if requested
145     pnd_pxml_merge_override ( pxmlh, disco_overrides );
146
147     // check for validity and add to resultset if it looks executable
148     if ( pnd_is_pxml_valid_app ( pxmlh ) ) {
149       pnd_disco_t *p;
150       char *fixpxml;
151
152       p = pnd_box_allocinsert ( disco_box, (char*) fpath, sizeof(pnd_disco_t) );
153
154       // base path
155       p -> path_to_object = strdup ( fpath );
156       if ( ( fixpxml = strcasestr ( p -> path_to_object, PXML_FILENAME ) ) ) {
157         *fixpxml = '\0'; // if this is not a .pnd, lop off the PXML.xml at the end
158       }
159
160       // png icon path
161       p -> pnd_icon_pos = pxml_close_pos;
162
163       // type
164       p -> object_type = valid;
165
166       // PXML fields
167       if ( pnd_pxml_get_app_name_en ( pxmlh ) ) {
168         p -> title_en = strdup ( pnd_pxml_get_app_name_en ( pxmlh ) );
169       }
170       if ( pnd_pxml_get_icon ( pxmlh ) ) {
171         p -> icon = strdup ( pnd_pxml_get_icon ( pxmlh ) );
172       }
173       if ( pnd_pxml_get_exec ( pxmlh ) ) {
174         p -> exec = strdup ( pnd_pxml_get_exec ( pxmlh ) );
175       }
176       if ( pnd_pxml_get_unique_id ( pxmlh ) ) {
177         p -> unique_id = strdup ( pnd_pxml_get_unique_id ( pxmlh ) );
178       }
179       if ( pnd_pxml_get_main_category ( pxmlh ) ) {
180         p -> main_category = strdup ( pnd_pxml_get_main_category ( pxmlh ) );
181       }
182       if ( pnd_pxml_get_clockspeed ( pxmlh ) ) {
183         p -> clockspeed = strdup ( pnd_pxml_get_clockspeed ( pxmlh ) ); 
184       }
185       if ( pnd_pxml_get_startdir ( pxmlh ) ) {
186         p -> startdir = strdup ( pnd_pxml_get_startdir ( pxmlh ) ); 
187       }
188
189     } else {
190       //printf ( "Invalid PXML; skipping.\n" );
191     }
192
193     // ditch pxml
194     pnd_pxml_delete ( pxmlh );
195
196   } // got a pxmlh
197
198   return ( 0 ); // continue the tree walk
199 }
200
201 pnd_box_handle pnd_disco_search ( char *searchpath, char *overridespath ) {
202
203   //printf ( "Searchpath to discover: '%s'\n", searchpath );
204
205   // alloc a container for the result set
206   disco_box = pnd_box_new ( "discovery" );
207   disco_overrides = overridespath;
208
209   /* iterate across the paths within the searchpath, attempting to locate applications
210    */
211
212   SEARCHPATH_PRE
213   {
214
215     // invoke the dir walking function; thankfully Linux includes a pretty good one
216     nftw ( buffer,               // path to descend
217            pnd_disco_callback,   // callback to do processing
218            10,                   // no more than X open fd's at once
219            FTW_PHYS );           // do not follow symlinks
220
221   }
222   SEARCHPATH_POST
223
224   // return whatever we found, or NULL if nada
225   if ( ! pnd_box_get_head ( disco_box ) ) {
226     pnd_box_delete ( disco_box );
227     disco_box = NULL;
228   }
229
230   return ( disco_box );
231 }
232
233 unsigned char pnd_emit_dotdesktop ( char *targetpath, char *pndrun, pnd_disco_t *p ) {
234   char filename [ FILENAME_MAX ];
235   char buffer [ 1024 ];
236   FILE *f;
237
238   // specification
239   // http://standards.freedesktop.org/desktop-entry-spec
240
241   // validation
242
243   if ( ! p -> unique_id ) {
244     return ( 0 );
245   }
246
247   if ( ! p -> exec ) {
248     return ( 0 );
249   }
250
251   // set up
252
253   sprintf ( filename, "%s/%s.desktop", targetpath, p -> unique_id );
254
255   // emit
256
257   //printf ( "EMIT DOTDESKTOP '%s'\n", filename );
258
259   f = fopen ( filename, "w" );
260
261   if ( ! f ) {
262     return ( 0 );
263   }
264
265   if ( p -> title_en ) {
266     snprintf ( buffer, 1020, "Name=%s\n", p -> title_en );
267     fprintf ( f, "%s", buffer );
268   }
269
270   fprintf ( f, "Type=Application\n" );
271   fprintf ( f, "Version=1.0\n" );
272
273   if ( p -> icon ) {
274     snprintf ( buffer, 1020, "Icon=%s\n", p -> icon );
275     fprintf ( f, "%s", buffer );
276   }
277
278 #if 0
279   if ( p -> description_en ) {
280     snprintf ( buffer, 1020, "Comment=%s\n", p -> description_en );
281     fprintf ( f, "%s", buffer );
282   }
283 #endif
284
285 #if 0
286   if ( p -> startdir ) {
287     snprintf ( buffer, 1020, "Path=%s\n", p -> startdir );
288     fprintf ( f, "%s", buffer );
289   } else {
290     fprintf ( f, "Path=%s\n", PND_DEFAULT_WORKDIR );
291   }
292 #endif
293
294   if ( p -> exec ) {
295     snprintf ( buffer, 1020, "Exec=%s -p %s -e %s -u\n", pndrun, p -> path_to_object, p -> exec );
296     fprintf ( f, "%s", buffer );
297   }
298
299   fprintf ( f, "_Source=libpnd\n" ); // should we need to know 'who' created the file during trimming
300
301   fclose ( f );
302
303   return ( 1 );
304 }
305
306 unsigned char pnd_emit_icon ( char *targetpath, pnd_disco_t *p ) {
307   char buffer [ FILENAME_MAX ];
308   char bits [ 8 * 1024 ];
309   unsigned int bitlen;
310
311   // filename
312   sprintf ( buffer, "%s/%u.png", targetpath, p -> unique_id );
313
314   // first.. are we looking through a pnd file or a dir?
315   if ( p -> object_type == pnd_object_type_directory ) {
316     // if we can find icon, copy it in from directory to destination
317
318   } else if ( p -> object_type == pnd_object_type_pnd ) {
319     // if we can get it from pnd file, copy it into destination
320
321     if ( ! p -> pnd_icon_pos ) {
322       return ( 0 ); // discover code didn't find it, so FAIL
323     }
324
325     FILE *pnd, *target;
326     unsigned int len;
327
328     pnd = fopen ( p -> path_to_object, "r" );
329
330     if ( ! pnd ) {
331       return ( 0 );
332     }
333
334     target = fopen ( buffer, "wb" );
335
336     if ( ! target ) {
337       fclose ( pnd );
338       return ( 0 );
339     }
340
341     fseek ( pnd, 0, SEEK_END );
342     len = ftell ( pnd );
343     //fseek ( pnd, 0, SEEK_SET );
344
345     fseek ( pnd, p -> pnd_icon_pos, SEEK_SET );
346
347     len -= p -> pnd_icon_pos;
348
349     while ( len ) {
350
351       if ( len > (8*1024) ) {
352         bitlen = (8*1024);
353       } else {
354         bitlen = len;
355       }
356
357       if ( fread ( bits, bitlen, 1, pnd ) != 1 ) {
358         fclose ( pnd );
359         fclose ( target );
360         unlink ( buffer );
361         return ( 0 );
362       }
363
364       if ( fwrite ( bits, bitlen, 1, target ) != 1 ) {
365         fclose ( pnd );
366         fclose ( target );
367         unlink ( buffer );
368         return ( 0 );
369       }
370
371       len -= bitlen;
372     } // while
373
374     fclose ( pnd );
375     fclose ( target );
376
377   }
378
379   return ( 1 );
380 }