6e44c2baf23cf0b526eaeeb44a1b5a540eeab7a8
[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 #define __USE_GNU /* for strcasestr */
16 #include <string.h>
17 #include <time.h>      // for time()
18 #include <ctype.h>     // for isdigit()
19 #include <sys/types.h> // for umask
20 #include <sys/stat.h>  // for umask
21 #include <dirent.h>    // for opendir()
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 static unsigned char g_daemon_mode = 0;
33
34 int main ( int argc, char *argv[] ) {
35   pnd_notify_handle nh;
36   // like discotest
37   char *configpath;
38   char *appspath;
39   char *overridespath;
40   // daemon stuff
41   char *searchpath = NULL;
42   char *dotdesktoppath = NULL;
43   // behaviour
44   unsigned char scanonlaunch = 1;
45   unsigned int interval_secs = 20;
46   // pnd runscript
47   char *run_searchpath;
48   char *run_script;
49   char *pndrun;
50   // misc
51   int i;
52
53   /* iterate across args
54    */
55   for ( i = 1; i < argc; i++ ) {
56
57     if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'd' ) {
58       //printf ( "Going daemon mode. Silent running.\n" );
59       g_daemon_mode = 1;
60     } else if ( isdigit ( argv [ i ][ 0 ] ) ) {
61       interval_secs = atoi ( argv [ i ] );
62     } else if ( argv [ i ][ 0 ] == '-' && argv [ i ][ 1 ] == 'n' ) {
63       scanonlaunch = 0;
64     } else {
65       printf ( "%s [-d] [##]\n", argv [ 0 ] );
66       printf ( "-d\tDaemon mode; detach from terminal, chdir to /tmp, suppress output. Optional.\n" );
67       printf ( "-n\tDo not scan on launch; default is to run a scan for apps when %s is invoked.\n", argv [ 0 ] );
68       printf ( "##\tA numeric value is interpreted as number of seconds between checking for filesystem changes. Default %u.\n",
69                interval_secs );
70       exit ( 0 );
71     }
72
73   }
74
75   if ( ! g_daemon_mode ) {
76     printf ( "Interval between checks is %u seconds\n", interval_secs );
77   }
78
79   // basic daemon set up
80   if ( g_daemon_mode ) {
81
82     // set a CWD somewhere else
83 #if 0
84     chdir ( "/tmp" );
85 #endif
86
87     // detach from terminal
88     if ( ( i = fork() ) < 0 ) {
89       printf ( "ERROR: Couldn't fork()\n" );
90       exit ( i );
91     }
92     if ( i ) {
93       exit ( 0 ); // exit parent
94     }
95     setsid();
96
97     // umask
98     umask ( 022 ); // emitted files can be rwxr-xr-x
99     
100   } // set up daemon
101
102   /* parse configs
103    */
104
105   // attempt to fetch a sensible default searchpath for configs
106   configpath = pnd_conf_query_searchpath();
107
108   // attempt to fetch the apps config to pick up a searchpath
109   pnd_conf_handle apph;
110
111   apph = pnd_conf_fetch_by_id ( pnd_conf_apps, configpath );
112
113   if ( apph ) {
114     appspath = pnd_conf_get_as_char ( apph, PND_APPS_KEY );
115
116     if ( ! appspath ) {
117       appspath = PND_APPS_SEARCHPATH;
118     }
119
120     overridespath = pnd_conf_get_as_char ( apph, PND_PXML_OVERRIDE_KEY );
121
122     if ( ! overridespath ) {
123       overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
124     }
125
126   } else {
127     // couldn't find a useful app search path so use the default
128     appspath = PND_APPS_SEARCHPATH;
129     overridespath = PND_PXML_OVERRIDE_SEARCHPATH;
130   }
131
132   // attempt to figure out where to drop dotfiles
133   pnd_conf_handle desktoph;
134
135   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, configpath );
136
137   if ( desktoph ) {
138     dotdesktoppath = pnd_conf_get_as_char ( desktoph, PND_DOTDESKTOP_KEY );
139
140     if ( ! dotdesktoppath ) {
141       dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
142     }
143
144   } else {
145     dotdesktoppath = PND_DOTDESKTOP_DEFAULT;
146   }
147
148   // try to locate a runscript
149
150   if ( apph ) {
151     run_searchpath = pnd_conf_get_as_char ( apph, PND_PNDRUN_SEARCHPATH_KEY );
152     run_script = pnd_conf_get_as_char ( apph, PND_PNDRUN_KEY );
153     pndrun = NULL;
154
155     if ( ! run_searchpath ) {
156       run_searchpath = PND_APPS_SEARCHPATH;
157       run_script = PND_PNDRUN_FILENAME;
158     }
159
160   } else {
161     run_searchpath = NULL;
162     run_script = NULL;
163     pndrun = PND_PNDRUN_DEFAULT;
164   }
165
166   if ( ! pndrun ) {
167     pndrun = pnd_locate_filename ( run_searchpath, run_script );
168
169     if ( ! pndrun ) {
170       pndrun = PND_PNDRUN_DEFAULT;
171     }
172
173   }
174
175   if ( ! g_daemon_mode ) {
176     if ( run_searchpath ) printf ( "Locating pnd run in %s\n", run_searchpath );
177     if ( run_script ) printf ( "Locating pnd runscript as %s\n", run_script );
178     if ( pndrun ) printf ( "Default pndrun is %s\n", pndrun );
179   }
180
181   /* handle globbing or variable substitution
182    */
183   dotdesktoppath = pnd_expand_tilde ( strdup ( dotdesktoppath ) );
184
185   /* validate paths
186    */
187   mkdir ( dotdesktoppath, 0777 );
188
189   /* startup
190    */
191
192   if ( ! g_daemon_mode ) {
193     printf ( "Apps searchpath is '%s'\n", appspath );
194     printf ( "PXML overrides searchpath is '%s'\n", overridespath );
195     printf ( ".desktop files emit to '%s'\n", dotdesktoppath );
196   }
197
198   /* set up notifies
199    */
200   searchpath = appspath;
201
202   nh = pnd_notify_init();
203
204   if ( ! nh ) {
205     if ( ! g_daemon_mode ) {
206       printf ( "INOTIFY failed to init.\n" );
207     }
208     exit ( -1 );
209   }
210
211   if ( ! g_daemon_mode ) {
212     printf ( "INOTIFY is up.\n" );
213   }
214
215   SEARCHPATH_PRE
216   {
217
218     if ( ! g_daemon_mode ) {
219       printf ( "Watching path '%s' and its descendents.\n", buffer );
220     }
221
222     pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
223
224   }
225   SEARCHPATH_POST
226
227   /* daemon main loop
228    */
229   while ( 1 ) {
230
231     // need to rediscover?
232     if ( scanonlaunch ||
233          pnd_notify_rediscover_p ( nh ) )
234     {
235       pnd_box_handle applist;
236       time_t createtime = time ( NULL ); // all 'new' .destops are created at or after this time; prev are old.
237
238       // if this was a forced scan, lets not do that next iteration
239       if ( scanonlaunch ) {
240         scanonlaunch = 0;
241       }
242
243       // by this point, the watched directories have notified us that something of relevent
244       // has occurred; we should be clever, but we're not, so just re-brute force the
245       // discovery and spit out .desktop files..
246       if ( ! g_daemon_mode ) {
247         printf ( "Changes within watched paths .. performing re-discover!\n" );
248         printf ( "Path to emit .desktop files to: '%s'\n", dotdesktoppath );
249       }
250
251       // run the discovery
252       applist = pnd_disco_search ( appspath, overridespath );
253
254       // list the found apps (if any)
255       if ( applist ) {
256         pnd_disco_t *d = pnd_box_get_head ( applist );
257
258         while ( d ) {
259
260           if ( ! g_daemon_mode ) {
261             printf ( "Found app: %s\n", pnd_box_get_key ( d ) );
262           }
263
264           // check if icon already exists (from a previous extraction say); if so, we needn't
265           // do it again
266           char existingpath [ FILENAME_MAX ];
267           sprintf ( existingpath, "%s/%s.png", dotdesktoppath, d -> unique_id );
268
269           struct stat dirs;
270           if ( stat ( existingpath, &dirs ) == 0 ) {
271             // icon seems to exist, so just crib the location into the .desktop
272
273             if ( ! g_daemon_mode ) {
274               printf ( "  Found icon already existed, so reusing it! %s\n", existingpath );
275             }
276
277             if ( d -> icon ) {
278               free ( d -> icon );
279             }
280             d -> icon = strdup ( existingpath );
281
282           } else {
283             // icon seems unreadable or does not exist; lets try to create it..
284
285             if ( ! g_daemon_mode ) {
286               printf ( "  Icon not already present, so trying to write it! %s\n", existingpath );
287             }
288
289             // attempt to create icon files; if successful, alter the disco struct to contain new
290             // path, otherwise leave it alone (since it could be a generic icon reference..)
291             if ( pnd_emit_icon ( dotdesktoppath, d ) ) {
292               // success; fix up icon path to new one..
293               if ( d -> icon ) {
294                 free ( d -> icon );
295               }
296               d -> icon = strdup ( existingpath );
297             }
298
299           } // icon already exists?
300
301           // create the .desktop file
302           if ( pnd_emit_dotdesktop ( dotdesktoppath, pndrun, d ) ) {
303             // add a watch onto the newly created .desktop?
304 #if 0
305             char buffer [ FILENAME_MAX ];
306             sprintf ( buffer, "%s/%s", dotdesktoppath, d -> unique_id );
307             pnd_notify_watch_path ( nh, buffer, PND_NOTIFY_RECURSE );
308 #endif
309           } else {
310             if ( ! g_daemon_mode ) {
311               printf ( "ERROR: Error creating .desktop file for app: %s\n", pnd_box_get_key ( d ) );
312             }
313           }
314
315           // next!
316           d = pnd_box_get_next ( d );
317
318         } // while applist
319
320       } else {
321
322         if ( ! g_daemon_mode ) {
323           printf ( "No applications found in search path\n" );
324         }
325
326       } // got apps?
327
328       // run a clean up, to remove any dotdesktop files that we didn't
329       // just now create (that seem to have been created by pndnotifyd
330       // previously.) This allows SD eject (or .pnd remove) to remove
331       // an app from the launcher
332       //   NOTE: Could opendir and iterate across all .desktop files,
333       // removing any that have Source= something else, and that the
334       // app name is not in the list found in applist box above. But
335       // a cheesy simple way right now is to just remove .desktop files
336       // that have a last mod time prior to the time we stored above.
337       {
338         DIR *dir;
339
340         if ( ( dir = opendir ( dotdesktoppath ) ) ) {
341           struct dirent *dirent;
342           struct stat dirs;
343           char buffer [ FILENAME_MAX ];
344
345           while ( ( dirent = readdir ( dir ) ) ) {
346
347             // file is a .desktop?
348             if ( strstr ( dirent -> d_name, ".desktop" ) == NULL ) {
349               continue;
350             }
351
352             // figure out full path
353             sprintf ( buffer, "%s/%s", dotdesktoppath, dirent -> d_name );
354
355             // file was previously created by libpnd; check Source= line
356             // logic: default to 'yes' (in case we can't open the file for some reason)
357             //        if we can open the file, default to no and look for the source flag we added; if
358             //          that matches then we know its libpnd created, otherwise assume not.
359             unsigned char source_libpnd = 1;
360             {
361               char line [ 256 ];
362               FILE *grep = fopen ( buffer, "r" );
363               if ( grep ) {
364                 source_libpnd = 0;
365                 while ( fgets ( line, 255, grep ) ) {
366                   if ( strcasestr ( line, PND_DOTDESKTOP_SOURCE ) ) {
367                     source_libpnd = 2;
368                   }
369                 } // while
370                 fclose ( grep );
371               }
372             }
373             if ( source_libpnd ) {
374               if ( ! g_daemon_mode ) {
375                 printf ( "File '%s' appears to have been created by libpnd so candidate for delete: %u\n", buffer, source_libpnd );
376               }
377             } else {
378               if ( ! g_daemon_mode ) {
379                 printf ( "File '%s' appears NOT to have been created by libpnd, so leave it alone\n", buffer );
380               }
381               continue; // skip deleting it
382             }
383
384             // file is 'new'?
385             if ( stat ( buffer, &dirs ) == 0 ) {
386               if ( dirs.st_mtime >= createtime ) {
387                 if ( ! g_daemon_mode ) {
388                   printf ( "File '%s' seems 'new', so leave it alone.\n", buffer );
389                 }
390                 continue; // skip deleting it
391               }
392             }
393
394             // by this point, the .desktop file must be 'old' and created by pndnotifyd
395             // previously, so can remove it
396             if ( ! g_daemon_mode ) {
397               printf ( "File '%s' seems to not belong; removing it.\n", dirent -> d_name );
398             }
399             unlink ( buffer );
400
401           } // while getting filenames from dir
402
403           closedir ( dir );
404         }
405
406       } // purge old .desktop files
407
408     } // need to rediscover?
409
410     // lets not eat up all the CPU
411     // should use an alarm or select() or something
412     sleep ( interval_secs );
413
414   } // while
415
416   /* shutdown
417    */
418   pnd_notify_shutdown ( nh );
419
420   return ( 0 );
421 }