5af0eb3ce9d267a746a4af44df2f822d30185853
[framework/uifw/efreet.git] / src / bin / efreet_desktop_cache_create.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7 #include <unistd.h>
8
9 #include <Eina.h>
10 #include <Eet.h>
11 #include <Ecore.h>
12 #include <Ecore_File.h>
13
14 #define EFREET_MODULE_LOG_DOM _efreet_desktop_cache_log_dom
15 static int _efreet_desktop_cache_log_dom = -1;
16
17 #include "Efreet.h"
18 #include "efreet_private.h"
19 #include "efreet_cache_private.h"
20
21 static Eet_Data_Descriptor *edd = NULL;
22 static Eet_File *ef = NULL;
23 static Eet_File *util_ef = NULL;
24
25 static Eina_Hash *desktops = NULL;
26
27 static Eina_Hash *file_ids = NULL;
28 static Eina_Hash *paths = NULL;
29
30 static Eina_Hash *mime_types = NULL;
31 static Eina_Hash *categories = NULL;
32 static Eina_Hash *startup_wm_class = NULL;
33 static Eina_Hash *name = NULL;
34 static Eina_Hash *generic_name = NULL;
35 static Eina_Hash *comment = NULL;
36 static Eina_Hash *exec = NULL;
37
38 static int
39 strcmplen(const void *data1, const void *data2)
40 {
41     return strncmp(data1, data2, eina_stringshare_strlen(data1));
42 }
43
44 static int
45 cache_add(const char *path, const char *file_id, int priority __UNUSED__, int *changed)
46 {
47     Efreet_Desktop *desk;
48     char *ext;
49
50     INF("FOUND: %s", path);
51     if (file_id) INF(" (id): %s", file_id);
52     ext = strrchr(path, '.');
53     if (!ext || (strcmp(ext, ".desktop") && strcmp(ext, ".directory"))) return 1;
54     desk = efreet_desktop_new(path);
55     if (desk) INF("  OK");
56     else      INF("  FAIL");
57     if (!desk) return 1;
58     if (!desk->eet)
59     {
60         /* This file isn't in cache */
61         *changed = 1;
62         INF("  NEW");
63     }
64     else if (ecore_file_mod_time(desk->orig_path) != desk->load_time)
65     {
66         efreet_desktop_free(desk);
67         *changed = 1;
68         desk = efreet_desktop_uncached_new(path);
69         if (desk) INF("  CHANGED");
70         else      INF("  NO UNCACHED");
71     }
72     if (!desk) return 1;
73     if (!eina_hash_find(paths, desk->orig_path))
74     {
75         if (!eet_data_write(ef, edd, desk->orig_path, desk, 0))
76             return 0;
77         eina_hash_add(paths, desk->orig_path, (void *)1);
78     }
79     /* TODO: We should check priority, and not just hope we search in right order */
80     /* TODO: We need to find out if prioritized file id has changed because of
81      * changed search order. */
82     if (!desk->hidden && desk->type == EFREET_DESKTOP_TYPE_APPLICATION &&
83         file_id && !eina_hash_find(file_ids, file_id))
84     {
85         Eina_List *l;
86         char *data;
87         Efreet_Cache_Array_String *array;
88
89 #define ADD_LIST(list, hash) \
90         EINA_LIST_FOREACH((list), l, data) \
91         { \
92             array = eina_hash_find((hash), data); \
93             if (!array) \
94                 array = NEW(Efreet_Cache_Array_String, 1); \
95             array->array = realloc(array->array, sizeof (char *) * (array->array_count + 1)); \
96             array->array[array->array_count++] = desk->orig_path; \
97             eina_hash_set((hash), data, array); \
98         }
99 #define ADD_ELEM(elem, hash) \
100         if ((elem)) \
101         { \
102             data = (elem); \
103             array = eina_hash_find((hash), data); \
104             if (!array) \
105                 array = NEW(Efreet_Cache_Array_String, 1); \
106             array->array = realloc(array->array, sizeof (char *) * (array->array_count + 1)); \
107             array->array[array->array_count++] = desk->orig_path; \
108             eina_hash_set((hash), data, array); \
109         }
110         ADD_LIST(desk->mime_types, mime_types);
111         ADD_LIST(desk->categories, categories);
112         ADD_ELEM(desk->startup_wm_class, startup_wm_class);
113         ADD_ELEM(desk->name, name);
114         ADD_ELEM(desk->generic_name, generic_name);
115         ADD_ELEM(desk->comment, comment);
116         ADD_ELEM(desk->exec, exec);
117         eina_hash_add(file_ids, file_id, desk->orig_path);
118         eina_hash_add(desktops, desk->orig_path, desk);
119     }
120     else
121         efreet_desktop_free(desk);
122     return 1;
123 }
124
125
126 static int
127 cache_scan(const char *path, const char *base_id, int priority, int recurse, int *changed)
128 {
129     char *file_id = NULL;
130     char id[PATH_MAX];
131     char buf[PATH_MAX];
132     DIR *files;
133     struct dirent *ent;
134
135     if (!ecore_file_is_dir(path)) return 1;
136
137     files = opendir(path);
138     if (!files) return 1;
139     id[0] = '\0';
140     while ((ent = readdir(files)))
141     {
142         if (!ent) break;
143         if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue;
144
145         if (base_id)
146         {
147             if (*base_id)
148                 snprintf(id, sizeof(id), "%s-%s", base_id, ent->d_name);
149             else
150                 strcpy(id, ent->d_name);
151             file_id = id;
152         }
153
154         snprintf(buf, sizeof(buf), "%s/%s", path, ent->d_name);
155         if (ecore_file_is_dir(buf))
156         {
157             if (recurse)
158                 cache_scan(buf, file_id, priority, recurse, changed);
159         }
160         else
161         {
162             if (!cache_add(buf, file_id, priority, changed))
163             {
164                 closedir(files);
165                 return 0;
166             }
167         }
168     }
169     closedir(files);
170     return 1;
171 }
172
173 static int
174 cache_lock_file(void)
175 {
176     char file[PATH_MAX];
177     struct flock fl;
178     int lockfd;
179
180     snprintf(file, sizeof(file), "%s/efreet/desktop_data.lock", efreet_cache_home_get());
181     lockfd = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
182     if (lockfd < 0) return -1;
183     efreet_fsetowner(lockfd);
184
185     memset(&fl, 0, sizeof(struct flock));
186     fl.l_type = F_WRLCK;
187     fl.l_whence = SEEK_SET;
188     if (fcntl(lockfd, F_SETLK, &fl) < 0)
189     {
190         INF("LOCKED! You may want to delete %s if this persists", file);
191         close(lockfd);
192         return -1;
193     }
194
195     return lockfd;
196 }
197
198 int
199 main(int argc, char **argv)
200 {
201     /* TODO:
202      * - Add file monitor on files, so that we catch changes on files
203      *   during whilst this program runs.
204      * - Maybe linger for a while to reduce number of cache re-creates.
205      */
206     Efreet_Cache_Hash hash;
207     Efreet_Cache_Version version;
208     Eina_List *dirs = NULL;
209     Eina_List *systemdirs = NULL;
210     Efreet_Cache_Array_String *user_dirs = NULL;
211     Eina_List *extra_dirs = NULL;
212     Eina_List *store_dirs = NULL;
213     int priority = 0;
214     char *dir = NULL;
215     char *path;
216     int lockfd = -1, tmpfd;
217     int changed = 0;
218     int i;
219     char file[PATH_MAX] = { '\0' };
220     char util_file[PATH_MAX] = { '\0' };
221
222     if (!eina_init()) goto eina_error;
223     _efreet_desktop_cache_log_dom =
224         eina_log_domain_register("efreet_desktop_cache", EFREET_DEFAULT_LOG_COLOR);
225     if (_efreet_desktop_cache_log_dom < 0)
226     {
227         EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_desktop_cache.");
228         return -1;
229     }
230
231     for (i = 1; i < argc; i++)
232     {
233         if (!strcmp(argv[i], "-v"))
234             eina_log_domain_level_set("efreet_desktop_cache", EINA_LOG_LEVEL_DBG);
235         else if ((!strcmp(argv[i], "-h")) ||
236                  (!strcmp(argv[i], "-help")) ||
237                  (!strcmp(argv[i], "--h")) ||
238                  (!strcmp(argv[i], "--help")))
239         {
240             printf("Options:\n");
241             printf("  -v              Verbose mode\n");
242             printf("  -d dir1 dir2    Extra dirs\n");
243             exit(0);
244         }
245         else if (!strcmp(argv[i], "-d"))
246         {
247             while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-'))
248                 extra_dirs = eina_list_append(extra_dirs, argv[++i]);
249         }
250     }
251     extra_dirs = eina_list_sort(extra_dirs, -1, EINA_COMPARE_CB(strcmp));
252
253     /* init external subsystems */
254     if (!eet_init()) goto eet_error;
255     if (!ecore_init()) goto ecore_error;
256
257     efreet_cache_update = 0;
258     /* finish efreet init */
259     if (!efreet_init()) goto efreet_error;
260
261     /* create homedir */
262     snprintf(file, sizeof(file), "%s/efreet", efreet_cache_home_get());
263     if (!ecore_file_exists(file))
264     {
265         if (!ecore_file_mkpath(file)) goto efreet_error;
266         efreet_setowner(file);
267     }
268
269     /* lock process, so that we only run one copy of this program */
270     lockfd = cache_lock_file();
271     if (lockfd == -1) goto efreet_error;
272
273     edd = efreet_desktop_edd();
274     if (!edd) goto edd_error;
275
276     /* read user dirs from old cache */
277     ef = eet_open(efreet_desktop_cache_file(), EET_FILE_MODE_READ);
278     if (ef)
279     {
280         user_dirs = eet_data_read(ef, efreet_array_string_edd(), EFREET_CACHE_DESKTOP_DIRS);
281         eet_close(ef);
282     }
283
284     /* create cache */
285     snprintf(file, sizeof(file), "%s.XXXXXX", efreet_desktop_cache_file());
286     tmpfd = mkstemp(file);
287     if (tmpfd < 0) goto error;
288     close(tmpfd);
289     ef = eet_open(file, EET_FILE_MODE_READ_WRITE);
290     if (!ef) goto error;
291
292     snprintf(util_file, sizeof(util_file), "%s.XXXXXX", efreet_desktop_util_cache_file());
293     tmpfd = mkstemp(util_file);
294     if (tmpfd < 0) goto error;
295     close(tmpfd);
296     util_ef = eet_open(util_file, EET_FILE_MODE_READ_WRITE);
297     if (!util_ef) goto error;
298
299     /* write cache version */
300     version.major = EFREET_DESKTOP_UTILS_CACHE_MAJOR;
301     version.minor = EFREET_DESKTOP_UTILS_CACHE_MINOR;
302     eet_data_write(util_ef, efreet_version_edd(), EFREET_CACHE_VERSION, &version, 1);
303     version.major = EFREET_DESKTOP_CACHE_MAJOR;
304     version.minor = EFREET_DESKTOP_CACHE_MINOR;
305     eet_data_write(ef, efreet_version_edd(), EFREET_CACHE_VERSION, &version, 1);
306
307     desktops = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_desktop_free));
308
309     file_ids = eina_hash_string_superfast_new(NULL);
310     paths = eina_hash_string_superfast_new(NULL);
311
312     mime_types = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
313     categories = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
314     startup_wm_class = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
315     name = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
316     generic_name = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
317     comment = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
318     exec = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free));
319
320     dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(),
321                                                                     "applications");
322     if (!dirs) goto error;
323
324     EINA_LIST_FREE(dirs, path)
325     {
326         char file_id[PATH_MAX] = { '\0' };
327
328         if (!cache_scan(path, file_id, priority++, 1, &changed)) goto error;
329         systemdirs = eina_list_append(systemdirs, path);
330     }
331
332     if (user_dirs)
333     {
334         unsigned int j;
335
336         for (j = 0; j < user_dirs->array_count; j++)
337         {
338             if (eina_list_search_unsorted_list(systemdirs, strcmplen, user_dirs->array[j]))
339                 continue;
340             if (!ecore_file_is_dir(user_dirs->array[j])) continue;
341             if (!cache_scan(user_dirs->array[j], NULL, priority, 0, &changed)) goto error;
342
343             store_dirs = eina_list_append(store_dirs, user_dirs->array[j]);
344         }
345         store_dirs = eina_list_sort(store_dirs, -1, EINA_COMPARE_CB(strcmp));
346     }
347
348     if (extra_dirs)
349     {
350         Eina_List *l;
351
352         EINA_LIST_FOREACH(extra_dirs, l, path)
353         {
354             if (eina_list_search_unsorted_list(systemdirs, strcmplen, path))
355                 continue;
356             if (eina_list_search_unsorted_list(store_dirs, EINA_COMPARE_CB(strcmp), path))
357                 continue;
358             if (!ecore_file_is_dir(path)) continue;
359
360             /* If we scan a passed dir, we must have changed */
361             changed = 1;
362             if (!cache_scan(path, NULL, priority, 0, &changed)) goto error;
363
364             store_dirs = eina_list_append(store_dirs, path);
365         }
366         store_dirs = eina_list_sort(store_dirs, -1, EINA_COMPARE_CB(strcmp));
367     }
368
369     if (user_dirs)
370     {
371         IF_FREE(user_dirs->array);
372         free(user_dirs);
373     }
374
375     /* store user dirs */
376     if (store_dirs)
377     {
378         Eina_List *l;
379
380         user_dirs = NEW(Efreet_Cache_Array_String, 1);
381         user_dirs->array = NEW(char *, eina_list_count(store_dirs));
382         user_dirs->array_count = 0;
383         EINA_LIST_FOREACH(store_dirs, l, path)
384             user_dirs->array[user_dirs->array_count++] = path;
385
386         eet_data_write(ef, efreet_array_string_edd(), EFREET_CACHE_DESKTOP_DIRS, user_dirs, 1);
387         IF_FREE(user_dirs->array);
388         free(user_dirs);
389     }
390
391     /* store util */
392 #define STORE_HASH_ARRAY(_hash) \
393     if (eina_hash_population((_hash)) > 0) \
394     { \
395         Eina_Iterator *it;   \
396         Efreet_Cache_Array_String array; \
397         const char *str;     \
398                              \
399         hash.hash = (_hash); \
400         eet_data_write(util_ef, efreet_hash_array_string_edd(), #_hash "_hash", &hash, 1); \
401         array.array_count = 0; \
402         array.array = malloc(eina_hash_population(hash.hash) * sizeof(char *)); \
403         it = eina_hash_iterator_key_new(hash.hash); \
404         EINA_ITERATOR_FOREACH(it, str) \
405             array.array[array.array_count++] = str; \
406         eina_iterator_free(it); \
407         eet_data_write(util_ef, efreet_array_string_edd(), #_hash "_list", &array, 1); \
408         free(array.array); \
409     }
410     STORE_HASH_ARRAY(mime_types);
411     STORE_HASH_ARRAY(categories);
412     STORE_HASH_ARRAY(startup_wm_class);
413     STORE_HASH_ARRAY(name);
414     STORE_HASH_ARRAY(generic_name);
415     STORE_HASH_ARRAY(comment);
416     STORE_HASH_ARRAY(exec);
417     if (eina_hash_population(file_ids) > 0)
418     {
419         hash.hash = file_ids;
420         eet_data_write(util_ef, efreet_hash_string_edd(), "file_id", &hash, 1);
421     }
422
423     eina_hash_free(mime_types);
424     eina_hash_free(categories);
425     eina_hash_free(startup_wm_class);
426     eina_hash_free(name);
427     eina_hash_free(generic_name);
428     eina_hash_free(comment);
429     eina_hash_free(exec);
430
431     eina_hash_free(file_ids);
432     eina_hash_free(paths);
433
434     eina_hash_free(desktops);
435
436     /* check if old and new caches contain the same number of entries */
437     if (!changed)
438     {
439         Eet_File *old;
440
441         old = eet_open(efreet_desktop_cache_file(), EET_FILE_MODE_READ);
442         if (!old || eet_num_entries(old) != eet_num_entries(ef)) changed = 1;
443         if (old) eet_close(old);
444         old = eet_open(efreet_desktop_util_cache_file(), EET_FILE_MODE_READ);
445         if (!old || eet_num_entries(old) != eet_num_entries(util_ef)) changed = 1;
446         if (old) eet_close(old);
447     }
448
449     /* cleanup */
450     eet_close(util_ef);
451     eet_close(ef);
452
453     /* unlink old cache files */
454     if (changed)
455     {
456         if (unlink(efreet_desktop_cache_file()) < 0)
457         {
458             if (errno != ENOENT) goto error;
459         }
460         if (unlink(efreet_desktop_util_cache_file()) < 0)
461         {
462             if (errno != ENOENT) goto error;
463         }
464         /* rename tmp files to real files */
465         if (rename(util_file, efreet_desktop_util_cache_file()) < 0) goto error;
466         efreet_setowner(efreet_desktop_util_cache_file());
467         if (rename(file, efreet_desktop_cache_file()) < 0) goto error;
468         efreet_setowner(efreet_desktop_cache_file());
469     }
470     else
471     {
472         unlink(util_file);
473         unlink(file);
474     }
475
476     /* touch update file */
477     snprintf(file, sizeof(file), "%s/efreet/desktop_data.update", efreet_cache_home_get());
478     tmpfd = open(file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
479     if (tmpfd >= 0)
480     {
481         char c = 'n';
482
483         efreet_fsetowner(tmpfd);
484         if (changed) c = 'c';
485         if (write(tmpfd, &c, 1) != 1) perror("write");
486         close(tmpfd);
487     }
488
489     EINA_LIST_FREE(systemdirs, dir)
490         eina_stringshare_del(dir);
491     eina_list_free(extra_dirs);
492     eina_list_free(store_dirs);
493     efreet_shutdown();
494     ecore_shutdown();
495     eet_shutdown();
496     eina_log_domain_unregister(_efreet_desktop_cache_log_dom);
497     eina_shutdown();
498     close(lockfd);
499     return 0;
500 error:
501     IF_FREE(dir);
502 edd_error:
503     if (user_dirs) efreet_cache_array_string_free(user_dirs);
504     efreet_shutdown();
505 efreet_error:
506     ecore_shutdown();
507 ecore_error:
508     eet_shutdown();
509 eet_error:
510     EINA_LIST_FREE(systemdirs, dir)
511         eina_stringshare_del(dir);
512     eina_list_free(extra_dirs);
513     eina_list_free(store_dirs);
514     eina_log_domain_unregister(_efreet_desktop_cache_log_dom);
515     eina_shutdown();
516 eina_error:
517     if (lockfd >= 0) close(lockfd);
518     return 1;
519 }