Mostly changes to pndnotify, to facilitate HUP to reload conf, and to add watches...
[pandora-libraries.git] / apps / pndnotifyd.c
1
2 /* pndnotifyd - a daemon whose job is to monitor searchpaths for app changes (appearing, disappearing, changing).
3  * If a change is found, the discovery code is invoked and apps registered for the launchers to see
4  *
5  */
6
7 // TODO: Catch HUP and reparse config
8 // TODO: Should perhaps direct all printf's through a vsprintf handler to avoid redundant "if ! g_daemon_mode"
9 // TODO: During daemon mode, should perhaps syslog or log errors
10
11 #include <stdio.h>     // for stdio
12 #include <unistd.h>    // for exit()
13 #include <stdlib.h>    // for exit()
14 #define __USE_GNU /* for strcasestr */
15 #include <string.h>
16 #include <time.h>      // for time()
17 #include <ctype.h>     // for isdigit()
18 #include <sys/types.h> // for umask
19 #include <sys/stat.h>  // for umask
20 #include <dirent.h>    // for opendir()
21 #include <signal.h>    // for sigaction
22
23 #include "pnd_conf.h"
24 #include "pnd_container.h"
25 #include "pnd_apps.h"
26 #include "pnd_notify.h"
27 #include "../lib/pnd_pathiter.h"
28 #include "pnd_discovery.h"
29 #include "pnd_locate.h"
30 #include "pnd_utility.h"
31
32 // this piece of code was simpler once; but need to grow it a bit and in a rush
33 // moving all these to globals rather than refactor the code a bit; tsk tsk..
34
35 // op mode; emitting stdout or no?
36 static unsigned char g_daemon_mode = 0;
37
38 // like discotest
39 char *configpath;
40 char *appspath;
41 char *overridespath;
42 // daemon stuff
43 char *searchpath = NULL;
44 char *dotdesktoppath = NULL;
45 // pnd runscript
46 char *run_searchpath;
47 char *run_script;
48 char *pndrun;
49 // notifier handle
50 pnd_notify_handle nh = 0;
51
52 // decl's
53 void consume_configuration ( void );
54 void setup_notifications ( void );
55 void sighup_handler ( int n );
56
57 int main ( int argc, char *argv[] ) {
58   // behaviour
59   unsigned char scanonlaunch = 1;
60   unsigned int interval_secs = 10;
61   // misc
62   int i;
63
64   /* iterate across args
65    */
66   for ( i = 1; i < argc; i++ ) {
67
68     if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
69       //printf ( "Going daemon mode. Silent running.\n" );
70       g_daemon_mode = 1;
71     } else if ( isdigit ( argv [ i ][ 0 ] ) ) {
72       interval_secs = atoi ( argv [ i ] );
73     } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'n' ) {
74       scanonlaunch = 0;
75     } else {
76       printf ( "%s [-d] [##]\n", argv [ 0 ] );
77       printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
78       printf ( "-n\tDo not scan on launch; default is to run a scan for apps when %s is invoked.\n", argv [ 0 ] );
79       printf ( "##\tA numeric value is interpreted as number of seconds between checking for filesystem changes. Default %u.\n",
80                interval_secs );
81       exit ( 0 );
82     }
83
84   }
85
86   if ( ! g_daemon_mode ) {
87     printf ( "Interval between checks is %u seconds\n", interval_secs );
88   }
89
90   // basic daemon set up
91   if ( g_daemon_mode ) {
92
93     // set a CWD somewhere else
94 #if 0
95     chdir ( "/tmp" );
96 #endif
97
98     // detach from terminal
99     if ( ( i = fork() ) < 0 ) {
100       printf ( "ERROR: Couldn't fork()\n" );
101       exit ( i );
102     }
103     if ( i ) {
104       exit ( 0 ); // exit parent
105     }
106     setsid();
107
108     // umask
109     umask ( 022 ); // emitted files can be rwxr-xr-x
110     
111   } // set up daemon
112
113   /* parse configs
114    */
115
116   consume_configuration();
117
118   /* startup
119    */
120
121   if ( ! g_daemon_mode ) {
122     printf ( "Apps searchpath is '%s'\n", appspath );
123     printf ( "PXML overrides searchpath is '%s'\n", overridespath );
124     printf ( ".desktop files emit to '%s'\n", dotdesktoppath );
125   }
126
127   /* set up signal handler
128    */
129   sigset_t ss;
130   sigemptyset ( &ss );
131
132   struct sigaction siggy;
133   siggy.sa_handler = sighup_handler;
134   siggy.sa_mask = ss; /* implicitly blocks the origin signal */
135   siggy.sa_flags = 0; /* don't need anything */
136
137   sigaction ( SIGHUP, &siggy, NULL );
138
139   /* set up notifies
140    */
141   setup_notifications();
142
143   /* daemon main loop
144    */
145   while ( 1 ) {
146
147     // need to rediscover?
148     if ( scanonlaunch ||
149          pnd_notify_rediscover_p ( nh ) )
150     {
151       pnd_box_handle applist;
152       time_t createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
153
154       // if this was a forced scan, lets not do that next iteration
155       if ( scanonlaunch ) {
156         scanonlaunch = 0;
157       }
158
159       // by this point, the watched directories have notified us that something of relevent
160       // has occurred; we should be clever, but we're not, so just re-brute force the
161       // discovery and spit out .desktop files..
162       if ( ! g_daemon_mode ) {
163         printf ( "Changes within watched paths .. performing re-discover!\n" );
164       }
165
166       // run the discovery
167       applist = pnd_disco_search ( appspath, overridespath );
168
169       // list the found apps (if any)
170       if ( applist ) {
171         pnd_disco_t *d = pnd_box_get_head ( applist );
172
173         while ( d ) {
174
175           if ( ! g_daemon_mode ) {
176             printf ( "Found app: %s\n", pnd_box_get_key ( d ) );
177           }
178
179           // check if icon already exists (from a previous extraction say); if so, we needn't
180           // do it again
181           char existingpath [ FILENAME_MAX ];
182           sprintf ( existingpath, "%s/%s.png", dotdesktoppath, d -> unique_id );
183
184           struct stat dirs;
185           if ( stat ( existingpath, &dirs ) == 0 ) {
186             // icon seems to exist, so just crib the location into the .desktop
187
188             if ( ! g_daemon_mode ) {
189               printf ( "  Found icon already existed, so reusing it! %s\n", existingpath );
190             }
191
192             if ( d -> icon ) {
193               free ( d -> icon );
194             }
195             d -> icon = strdup ( existingpath );
196
197           } else {
198             // icon seems unreadable or does not exist; lets try to create it..
199
200             if ( ! g_daemon_mode ) {
201               printf ( "  Icon not already present, so trying to write it! %s\n", existingpath );
202             }
203
204             // attempt to create icon files; if successful, alter the disco struct to contain new
205             // path, otherwise leave it alone (since it could be a generic icon reference..)
206             if ( pnd_emit_icon ( dotdesktoppath, d ) ) {
207               // success; fix up icon path to new one..
208               if ( d -> icon ) {
209                 free ( d -> icon );
210               }
211               d -> icon = strdup ( existingpath );
212             }
213
214           } // icon already exists?
215
216           // create the .desktop file
217           if ( pnd_emit_dotdesktop ( dotdesktoppath, pndrun, d ) ) {
218             // add a watch onto the newly created .desktop?
219 #if 0
220             char buffer [ FILENAME_MAX ];
221             sprintf ( buffer, "%s/%s", dotdesktoppath, d -> unique_id );
222             pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
223 #endif
224           } else {
225             if ( ! g_daemon_mode ) {
226               printf ( "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
227             }
228           }
229
230           // next!
231           d = pnd_box_get_next ( d );
232
233         } // while applist
234
235       } else {
236
237         if ( ! g_daemon_mode ) {
238           printf ( "No applications found in search path\n" );
239         }
240
241       } // got apps?
242
243       // run a clean up, to remove any dotdesktop files that we didn't
244       // just now create (that seem to have been created by pndnotifyd
245       // previously.) This allows SD eject (or .pnd remove) to remove
246       // an app from the launcher
247       //   NOTE: Could opendir and iterate across all .desktop files,
248       // removing any that have Source= something else, and that the
249       // app name is not in the list found in applist box above. But
250       // a cheesy simple way right now is to just remove .desktop files
251       // that have a last mod time prior to the time we stored above.
252       {
253         DIR *dir;
254
255         if ( ( dir = opendir ( dotdesktoppath ) ) ) {
256           struct dirent *dirent;
257           struct stat dirs;
258           char buffer [ FILENAME_MAX ];
259
260           while ( ( dirent = readdir ( dir ) ) ) {
261
262             // file is a .desktop?
263             if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
264               continue;
265             }
266
267             // figure out full path
268             sprintf ( buffer, "%s/%s", dotdesktoppath, dirent -> d_name );
269
270             // file was previously created by libpnd; check Source= line
271             // logic: default to 'yes' (in case we can't open the file for some reason)
272             //        if we can open the file, default to no and look for the source flag we added; if
273             //          that matches then we know its libpnd created, otherwise assume not.
274             unsigned char source_libpnd = 1;
275             {
276               char line [ 256 ];
277               FILE *grep = fopen ( buffer, "r" );
278               if ( grep ) {
279                 source_libpnd = 0;
280                 while ( fgets ( line, 255, grep ) ) {
281                   if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) {
282                     source_libpnd = 2;
283                   }
284                 } // while
285                 fclose ( grep );
286               }
287             }
288             if ( source_libpnd ) {
289 #if 0
290               if ( ! g_daemon_mode ) {
291                 printf ( "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd );
292               }
293 #endif
294             } else {
295 #if 0
296               if ( ! g_daemon_mode ) {
297                 printf ( "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
298               }
299 #endif
300               continue; // skip deleting it
301             }
302
303             // file is 'new'?
304             if ( stat ( buffer, &dirs ) == 0 ) {
305               if ( dirs.st_mtime >= createtime ) {
306 #if 0
307                 if ( ! g_daemon_mode ) {
308                   printf ( "File '%s' seems 'new', so leave it alone.\n", buffer );
309                 }
310 #endif
311                 continue; // skip deleting it
312               }
313             }
314
315             // by this point, the .desktop file must be 'old' and created by pndnotifyd
316             // previously, so can remove it
317             if ( ! g_daemon_mode ) {
318               printf ( "File '%s' seems nolonger relevent; removing it.\n", dirent -> d_name );
319             }
320             unlink ( buffer );
321
322           } // while getting filenames from dir
323
324           closedir ( dir );
325         }
326
327       } // purge old .desktop files
328
329       // since its entirely likely new directories have been found (ie: SD with a directory structure was inserted)
330       // we should re-apply watches to catch all these new directories; ie: user might use on-device browser to
331       // drop in new applications, or use the shell to juggle them around, or any number of activities.
332       setup_notifications();
333
334     } // need to rediscover?
335
336     // lets not eat up all the CPU
337     // should use an alarm or select() or something
338     sleep ( interval_secs );
339
340   } // while
341
342   /* shutdown
343    */
344   pnd_notify_shutdown ( nh );
345
346   return ( 0 );
347 }
348
349 void consume_configuration ( void ) {
350
351   // attempt to fetch a sensible default searchpath for configs
352   configpath = pnd_conf_query_searchpath();
353
354   // attempt to fetch the apps config to pick up a searchpath
355   pnd_conf_handle apph;
356
357   apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
358
359   if ( apph ) {
360     appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY );
361
362     if ( ! appspath ) {
363       appspath = PND_APPS_SEARCHPATH;
364     }
365
366     overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY );
367
368     if ( ! overridespath ) {
369       overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
370     }
371
372   } else {
373     // couldn't find a useful app search path so use the default
374     appspath = PND_APPS_SEARCHPATH;
375     overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
376   }
377
378   // attempt to figure out where to drop dotfiles
379   pnd_conf_handle desktoph;
380
381   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath );
382
383   if ( desktoph ) {
384     dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOP_KEY );
385
386     if ( ! dotdesktoppath ) {
387       dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
388     }
389
390   } else {
391     dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
392   }
393
394   // try to locate a runscript
395
396   if ( apph ) {
397     run_searchpath = pnd_conf_get_as_char ( apph, PND_PNDRUN_SEARCHPATH_KEY );
398     run_script = pnd_conf_get_as_char ( apph, PND_PNDRUN_KEY );
399     pndrun = NULL;
400
401     if ( ! run_searchpath ) {
402       run_searchpath = PND_APPS_SEARCHPATH;
403       run_script = PND_PNDRUN_FILENAME;
404     }
405
406   } else {
407     run_searchpath = NULL;
408     run_script = NULL;
409     pndrun = PND_PNDRUN_DEFAULT;
410   }
411
412   if ( ! pndrun ) {
413     pndrun = pnd_locate_filename ( run_searchpath, run_script );
414
415     if ( ! pndrun ) {
416       pndrun = PND_PNDRUN_DEFAULT;
417     }
418
419   }
420
421   if ( ! g_daemon_mode ) {
422     if ( run_searchpath ) printf ( "Locating pnd run in %s\n", run_searchpath );
423     if ( run_script ) printf ( "Locating pnd runscript as %s\n", run_script );
424     if ( pndrun ) printf ( "Default pndrun is %s\n", pndrun );
425   }
426
427   /* handle globbing or variable substitution
428    */
429   dotdesktoppath = pnd_expand_tilde ( strdup ( dotdesktoppath ) );
430
431   /* validate paths
432    */
433   mkdir ( dotdesktoppath, 0777 );
434
435   // done
436   return;
437 }
438
439 void setup_notifications ( void ) {
440   searchpath = appspath;
441
442   // if this is first time through, we can just set it up; for subsequent times
443   // through, we need to close existing fd and re-open it, since we're too lame
444   // to store the list of watches and 'rm' them
445   if ( nh ) {
446     pnd_notify_shutdown ( nh );
447   }
448
449   // set up a new set of notifies
450   nh = pnd_notify_init();
451
452   if ( ! nh ) {
453     if ( ! g_daemon_mode ) {
454       printf ( "INOTIFY failed to init.\n" );
455     }
456     exit ( -1 );
457   }
458
459 #if 0
460   if ( ! g_daemon_mode ) {
461     printf ( "INOTIFY is up.\n" );
462   }
463 #endif
464
465   SEARCHPATH_PRE
466   {
467
468     if ( ! g_daemon_mode ) {
469       printf ( "Watching path '%s' and its descendents.\n", buffer );
470     }
471
472     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
473
474   }
475   SEARCHPATH_POST
476
477   return;
478 }
479
480 void sighup_handler ( int n ) {
481
482   // reparse config files
483   consume_configuration();
484
485   // re set up the notifier watches
486   setup_notifications();
487
488   return;
489 }