Added first stab of pnd exec support
[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 // TODO: Removing stale .desktop checks that .desktop was created by libpnd; see 'TBD' below
11
12 #include <stdio.h>     // for stdio
13 #include <unistd.h>    // for exit()
14 #include <stdlib.h>    // for exit()
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
22 #include "pnd_conf.h"
23 #include "pnd_container.h"
24 #include "pnd_apps.h"
25 #include "pnd_notify.h"
26 #include "../lib/pnd_pathiter.h"
27 #include "pnd_discovery.h"
28 #include "pnd_locate.h"
29
30 static unsigned char g_daemon_mode = 0;
31
32 int main ( int argc, char *argv[] ) {
33   pnd_notify_handle nh;
34   // like discotest
35   char *configpath;
36   char *appspath;
37   char *overridespath;
38   // daemon stuff
39   char *searchpath = NULL;
40   char *dotdesktoppath = NULL;
41   // behaviour
42   unsigned char scanonlaunch = 1;
43   unsigned int interval_secs = 20;
44   // pnd runscript
45   char *run_searchpath;
46   char *run_script;
47   char *pndrun;
48   // misc
49   int i;
50
51   /* iterate across args
52    */
53   for ( i = 1; i < argc; i++ ) {
54
55     if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
56       //printf ( "Going daemon mode. Silent running.\n" );
57       g_daemon_mode = 1;
58     } else if ( isdigit ( argv [ i ][ 0 ] ) ) {
59       interval_secs = atoi ( argv [ i ] );
60     } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'n' ) {
61       scanonlaunch = 0;
62     } else {
63       printf ( "%s [-d] [##]\n", argv [ 0 ] );
64       printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
65       printf ( "-n\tDo not scan on launch; default is to run a scan for apps when %s is invoked.\n", argv [ 0 ] );
66       printf ( "##\tA numeric value is interpreted as number of seconds between checking for filesystem changes. Default %u.\n",
67                interval_secs );
68       exit ( 0 );
69     }
70
71   }
72
73   if ( ! g_daemon_mode ) {
74     printf ( "Interval between checks is %u seconds\n", interval_secs );
75   }
76
77   // basic daemon set up
78   if ( g_daemon_mode ) {
79
80     // set a CWD somewhere else
81 #if 0
82     chdir ( "/tmp" );
83 #endif
84
85     // detach from terminal
86     if ( ( i = fork() ) < 0 ) {
87       printf ( "ERROR: Couldn't fork()\n" );
88       exit ( i );
89     }
90     if ( i ) {
91       exit ( 0 ); // exit parent
92     }
93     setsid();
94
95     // umask
96     umask ( 022 ); // emitted files can be rwxr-xr-x
97     
98   } // set up daemon
99
100   /* parse configs
101    */
102
103   // attempt to fetch a sensible default searchpath for configs
104   configpath = pnd_conf_query_searchpath();
105
106   // attempt to fetch the apps config to pick up a searchpath
107   pnd_conf_handle apph;
108
109   apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
110
111   if ( apph ) {
112     appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY );
113
114     if ( ! appspath ) {
115       appspath = PND_APPS_SEARCHPATH;
116     }
117
118     overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY );
119
120     if ( ! overridespath ) {
121       overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
122     }
123
124   } else {
125     // couldn't find a useful app search path so use the default
126     appspath = PND_APPS_SEARCHPATH;
127     overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
128   }
129
130   // attempt to figure out where to drop dotfiles
131   pnd_conf_handle desktoph;
132
133   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath );
134
135   if ( desktoph ) {
136     dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOP_KEY );
137
138     if ( ! dotdesktoppath ) {
139       dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
140     }
141
142   } else {
143     dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
144   }
145
146   // try to locate a runscript
147
148   if ( apph ) {
149     run_searchpath = pnd_conf_get_as_char ( apph, PND_PNDRUN_SEARCHPATH_KEY );
150     run_script = pnd_conf_get_as_char ( apph, PND_PNDRUN_KEY );
151     pndrun = NULL;
152
153     if ( ! run_searchpath ) {
154       run_searchpath = PND_APPS_SEARCHPATH;
155       run_script = PND_PNDRUN_FILENAME;
156     }
157
158   } else {
159     run_searchpath = NULL;
160     run_script = NULL;
161     pndrun = PND_PNDRUN_DEFAULT;
162   }
163
164   if ( ! pndrun ) {
165     pndrun = pnd_locate_filename ( run_searchpath, run_script );
166
167     if ( ! pndrun ) {
168       pndrun = PND_PNDRUN_DEFAULT;
169     }
170
171   }
172
173   if ( ! g_daemon_mode ) {
174     if ( run_searchpath ) printf ( "Locating pnd run in %s\n", run_searchpath );
175     if ( run_script ) printf ( "Locating pnd runscript as %s\n", run_script );
176     if ( pndrun ) printf ( "Default pndrun is %s\n", pndrun );
177   }
178
179   /* startup
180    */
181
182   if ( ! g_daemon_mode ) {
183     printf ( "Apps searchpath is '%s'\n", appspath );
184     printf ( "PXML overrides searchpath is '%s'\n", overridespath );
185     printf ( ".desktop files emit to '%s'\n", dotdesktoppath );
186   }
187
188   /* set up notifies
189    */
190   searchpath = appspath;
191
192   nh = pnd_notify_init();
193
194   if ( ! nh ) {
195     if ( ! g_daemon_mode ) {
196       printf ( "INOTIFY failed to init.\n" );
197     }
198     exit ( -1 );
199   }
200
201   if ( ! g_daemon_mode ) {
202     printf ( "INOTIFY is up.\n" );
203   }
204
205   SEARCHPATH_PRE
206   {
207
208     if ( ! g_daemon_mode ) {
209       printf ( "Watching path '%s' and its descendents.\n", buffer );
210     }
211
212     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
213
214   }
215   SEARCHPATH_POST
216
217   /* daemon main loop
218    */
219   while ( 1 ) {
220
221     // need to rediscover?
222     if ( scanonlaunch ||
223          pnd_notify_rediscover_p ( nh ) )
224     {
225       pnd_box_handle applist;
226       time_t createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
227
228       // if this was a forced scan, lets not do that next iteration
229       if ( scanonlaunch ) {
230         scanonlaunch = 0;
231       }
232
233       // by this point, the watched directories have notified us that something of relevent
234       // has occurred; we should be clever, but we're not, so just re-brute force the
235       // discovery and spit out .desktop files..
236       if ( ! g_daemon_mode ) {
237         printf ( "Changes within watched paths .. performing re-discover!\n" );
238         printf ( "Path to emit .desktop files to: '%s'\n", dotdesktoppath );
239       }
240
241       // run the discovery
242       applist = pnd_disco_search ( appspath, overridespath );
243
244       // list the found apps (if any)
245       if ( applist ) {
246         pnd_disco_t *d = pnd_box_get_head ( applist );
247
248         while ( d ) {
249
250           if ( ! g_daemon_mode ) {
251             printf ( "Found app: %s\n", pnd_box_get_key ( d ) );
252           }
253
254           // create the .desktop file
255           if ( pnd_emit_dotdesktop ( dotdesktoppath, pndrun, d ) ) {
256             // add a watch onto the newly created .desktop?
257 #if 0
258             char buffer [ FILENAME_MAX ];
259             sprintf ( buffer, "%s/%s", dotdesktoppath, d -> unique_id );
260             pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
261 #endif
262           } else {
263             if ( ! g_daemon_mode ) {
264               printf ( "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
265             }
266           }
267
268           // next!
269           d = pnd_box_get_next ( d );
270
271         } // while applist
272
273       } else {
274
275         if ( ! g_daemon_mode ) {
276           printf ( "No applications found in search path\n" );
277         }
278
279       } // got apps?
280
281       // run a clean up, to remove any dotdesktop files that we didn't
282       // just now create (that seem to have been created by pndnotifyd
283       // previously.) This allows SD eject (or .pnd remove) to remove
284       // an app from the launcher
285       //   NOTE: Could opendir and iterate across all .desktop files,
286       // removing any that have Source= something else, and that the
287       // app name is not in the list found in applist box above. But
288       // a cheesy simple way right now is to just remove .desktop files
289       // that have a last mod time prior to the time we stored above.
290       {
291         DIR *dir;
292
293         if ( ( dir = opendir ( dotdesktoppath ) ) ) {
294           struct dirent *dirent;
295           struct stat dirs;
296           char buffer [ FILENAME_MAX ];
297
298           while ( ( dirent = readdir ( dir ) ) ) {
299
300             // file is a .desktop?
301             if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
302               continue;
303             }
304
305             // file was previously created by libpnd; check Source= line
306             // TBD
307
308             // file is 'new'?
309             sprintf ( buffer, "%s/%s", dotdesktoppath, dirent -> d_name );
310             if ( stat ( buffer, &dirs ) == 0 ) {
311               if ( dirs.st_mtime >= createtime ) {
312                 continue;
313               }
314             }
315
316             // by this point, the .desktop file must be 'old' and created by pndnotifyd
317             // previously, so can remove it
318             if ( ! g_daemon_mode ) {
319               printf ( "File '%s' seems to not belong; removing it.\n", dirent -> d_name );
320             }
321             unlink ( buffer );
322
323           } // while getting filenames from dir
324
325           closedir ( dir );
326         }
327
328       } // purge old .desktop files
329
330     } // need to rediscover?
331
332     // lets not eat up all the CPU
333     // should use an alarm or select() or something
334     sleep ( interval_secs );
335
336   } // while
337
338   /* shutdown
339    */
340   pnd_notify_shutdown ( nh );
341
342   return ( 0 );
343 }