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
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
11 #include <stdio.h> // for stdio
12 #include <unistd.h> // for exit()
13 #include <stdlib.h> // for exit()
14 #define __USE_GNU /* for strcasestr */
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
24 #include "pnd_container.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"
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..
35 // op mode; emitting stdout or no?
36 static unsigned char g_daemon_mode = 0;
43 char *searchpath = NULL;
44 char *dotdesktoppath = NULL;
50 pnd_notify_handle nh = 0;
53 void consume_configuration ( void );
54 void setup_notifications ( void );
55 void sighup_handler ( int n );
57 int main ( int argc, char *argv[] ) {
59 unsigned char scanonlaunch = 1;
60 unsigned int interval_secs = 10;
64 /* iterate across args
66 for ( i = 1; i < argc; i++ ) {
68 if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
69 //printf ( "Going daemon mode. Silent running.\n" );
71 } else if ( isdigit ( argv [ i ][ 0 ] ) ) {
72 interval_secs = atoi ( argv [ i ] );
73 } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'n' ) {
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",
86 if ( ! g_daemon_mode ) {
87 printf ( "Interval between checks is %u seconds\n", interval_secs );
90 // basic daemon set up
91 if ( g_daemon_mode ) {
93 // set a CWD somewhere else
98 // detach from terminal
99 if ( ( i = fork() ) < 0 ) {
100 printf ( "ERROR: Couldn't fork()\n" );
104 exit ( 0 ); // exit parent
109 umask ( 022 ); // emitted files can be rwxr-xr-x
116 consume_configuration();
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 );
127 /* set up signal handler
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 */
137 sigaction ( SIGHUP, &siggy, NULL );
141 setup_notifications();
147 // need to rediscover?
149 pnd_notify_rediscover_p ( nh ) )
151 pnd_box_handle applist;
152 time_t createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
154 // if this was a forced scan, lets not do that next iteration
155 if ( scanonlaunch ) {
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" );
167 applist = pnd_disco_search ( appspath, overridespath );
169 // list the found apps (if any)
171 pnd_disco_t *d = pnd_box_get_head ( applist );
175 if ( ! g_daemon_mode ) {
176 printf ( "Found app: %s\n", pnd_box_get_key ( d ) );
179 // check if icon already exists (from a previous extraction say); if so, we needn't
181 char existingpath [ FILENAME_MAX ];
182 sprintf ( existingpath, "%s/%s.png", dotdesktoppath, d -> unique_id );
185 if ( stat ( existingpath, &dirs ) == 0 ) {
186 // icon seems to exist, so just crib the location into the .desktop
188 if ( ! g_daemon_mode ) {
189 printf ( " Found icon already existed, so reusing it! %s\n", existingpath );
195 d -> icon = strdup ( existingpath );
198 // icon seems unreadable or does not exist; lets try to create it..
200 if ( ! g_daemon_mode ) {
201 printf ( " Icon not already present, so trying to write it! %s\n", existingpath );
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..
211 d -> icon = strdup ( existingpath );
214 } // icon already exists?
216 // create the .desktop file
217 if ( pnd_emit_dotdesktop ( dotdesktoppath, pndrun, d ) ) {
218 // add a watch onto the newly created .desktop?
220 char buffer [ FILENAME_MAX ];
221 sprintf ( buffer, "%s/%s", dotdesktoppath, d -> unique_id );
222 pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
225 if ( ! g_daemon_mode ) {
226 printf ( "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
231 d = pnd_box_get_next ( d );
237 if ( ! g_daemon_mode ) {
238 printf ( "No applications found in search path\n" );
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.
255 if ( ( dir = opendir ( dotdesktoppath ) ) ) {
256 struct dirent *dirent;
258 char buffer [ FILENAME_MAX ];
260 while ( ( dirent = readdir ( dir ) ) ) {
262 // file is a .desktop?
263 if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
267 // figure out full path
268 sprintf ( buffer, "%s/%s", dotdesktoppath, dirent -> d_name );
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;
277 FILE *grep = fopen ( buffer, "r" );
280 while ( fgets ( line, 255, grep ) ) {
281 if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) {
288 if ( source_libpnd ) {
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 );
296 if ( ! g_daemon_mode ) {
297 printf ( "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
300 continue; // skip deleting it
304 if ( stat ( buffer, &dirs ) == 0 ) {
305 if ( dirs.st_mtime >= createtime ) {
307 if ( ! g_daemon_mode ) {
308 printf ( "File '%s' seems 'new', so leave it alone.\n", buffer );
311 continue; // skip deleting it
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 );
322 } // while getting filenames from dir
327 } // purge old .desktop files
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();
334 } // need to rediscover?
336 // lets not eat up all the CPU
337 // should use an alarm or select() or something
338 sleep ( interval_secs );
344 pnd_notify_shutdown ( nh );
349 void consume_configuration ( void ) {
351 // attempt to fetch a sensible default searchpath for configs
352 configpath = pnd_conf_query_searchpath();
354 // attempt to fetch the apps config to pick up a searchpath
355 pnd_conf_handle apph;
357 apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
360 appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY );
363 appspath = PND_APPS_SEARCHPATH;
366 overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY );
368 if ( ! overridespath ) {
369 overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
373 // couldn't find a useful app search path so use the default
374 appspath = PND_APPS_SEARCHPATH;
375 overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
378 // attempt to figure out where to drop dotfiles
379 pnd_conf_handle desktoph;
381 desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath );
384 dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOP_KEY );
386 if ( ! dotdesktoppath ) {
387 dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
391 dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
394 // try to locate a runscript
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 );
401 if ( ! run_searchpath ) {
402 run_searchpath = PND_APPS_SEARCHPATH;
403 run_script = PND_PNDRUN_FILENAME;
407 run_searchpath = NULL;
409 pndrun = PND_PNDRUN_DEFAULT;
413 pndrun = pnd_locate_filename ( run_searchpath, run_script );
416 pndrun = PND_PNDRUN_DEFAULT;
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 );
427 /* handle globbing or variable substitution
429 dotdesktoppath = pnd_expand_tilde ( strdup ( dotdesktoppath ) );
433 mkdir ( dotdesktoppath, 0777 );
439 void setup_notifications ( void ) {
440 searchpath = appspath;
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
446 pnd_notify_shutdown ( nh );
449 // set up a new set of notifies
450 nh = pnd_notify_init();
453 if ( ! g_daemon_mode ) {
454 printf ( "INOTIFY failed to init.\n" );
460 if ( ! g_daemon_mode ) {
461 printf ( "INOTIFY is up.\n" );
468 if ( ! g_daemon_mode ) {
469 printf ( "Watching path '%s' and its descendents.\n", buffer );
472 pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
480 void sighup_handler ( int n ) {
482 // reparse config files
483 consume_configuration();
485 // re set up the notifier watches
486 setup_notifications();