3
* Copyright (c) 2006 Benedikt Meurer <benny@xfce.org>
5
* This program is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License as published by the Free
7
* Software Foundation; either version 2 of the License, or (at your option)
10
* This program is distributed in the hope that it will be useful, but WITHOUT
11
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15
* You should have received a copy of the GNU General Public License along with
16
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17
* Place, Suite 330, Boston, MA 02111-1307 USA
20
/********************************************************************************
23
* thunar-vfs-mime-cleaner is a small utility program, used to cleanup the *
24
* $XDG_DATA_HOME/applications/ folder, in which both Thunar and Nautilus *
25
* create <app>-usercreated.desktop files when the user enters a custom *
26
* command to open a file. These .desktop files aren't removed by the package *
27
* manager when the associated applications are removed, so we do this manu- *
28
* ally using this simple tool. *
30
* In addition, the defaults.list file will be cleaned and references to dead *
31
* desktop-ids will be removed from the list. *
32
********************************************************************************/
38
#ifdef HAVE_SYS_TYPES_H
39
#include <sys/types.h>
62
#include <glib/gstdio.h>
63
#include <libxfce4util/libxfce4util.h>
68
check_exec (const gchar *exec)
70
gboolean result = TRUE;
74
/* exec = NULL is invalid */
75
if (G_UNLIKELY (exec == NULL))
78
/* try to parse the "Exec" line */
79
if (g_shell_parse_argv (exec, NULL, &argv, NULL))
81
/* check if argv[0] is an absolute path to an existing file */
82
result = (g_path_is_absolute (argv[0]) && g_file_test (argv[0], G_FILE_TEST_EXISTS));
84
/* otherwise, argv[0] must be a program in $PATH */
85
if (G_LIKELY (!result))
87
/* search in $PATH for argv[0] */
88
command = g_find_program_in_path (argv[0]);
89
result = (command != NULL);
93
/* release the argv */
103
load_defaults_list (const gchar *directory)
107
GHashTable *defaults_list;
116
/* allocate a new hash table for the defaults.list */
117
defaults_list = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_strfreev);
119
/* determine the path to the defaults.list */
120
path = g_build_filename (directory, "defaults.list", NULL);
122
/* try to open the defaults.list file */
123
fp = fopen (path, "r");
124
if (G_LIKELY (fp != NULL))
126
/* process the file */
127
while (fgets (line, sizeof (line), fp) != NULL)
129
/* skip everything that doesn't look like a mime type line */
130
for (lp = line; g_ascii_isspace (*lp); ++lp) ;
131
if (G_UNLIKELY (*lp < 'a' || *lp > 'z'))
135
for (type = lp++; *lp != '\0' && *lp != '='; ++lp) ;
136
if (G_UNLIKELY (*lp != '='))
140
/* extract the application id list */
141
for (ids = lp; g_ascii_isgraph (*lp); ++lp) ;
142
if (G_UNLIKELY (ids == lp))
146
/* parse the id list */
147
id_list = g_strsplit (ids, ";", -1);
148
for (m = n = 0; id_list[m] != NULL; ++m)
150
if (G_UNLIKELY (id_list[m][0] == '\0'))
153
id_list[n++] = id_list[m];
157
/* verify that the id list is not empty */
158
if (G_UNLIKELY (id_list[0] == NULL))
164
/* insert the line into the hash table */
165
g_hash_table_insert (defaults_list, g_strdup (type), id_list);
175
return defaults_list;
181
check_desktop_id (const gchar *id)
187
/* check if we can load a .desktop file for the given desktop-id */
188
key_file = g_key_file_new ();
189
path = g_build_filename ("applications", id, NULL);
190
result = g_key_file_load_from_data_dirs (key_file, path, NULL, G_KEY_FILE_NONE, NULL);
191
g_key_file_free (key_file);
201
GHashTable *replacements;
208
save_defaults_list_one (const gchar *type,
210
SaveDescriptor *save_descriptor)
212
const gchar *replacement_id;
215
/* perform the required transformations on the id list */
216
for (i = j = 0; ids[i] != NULL; ++i)
218
/* check if we have a replacement for this id */
219
replacement_id = g_hash_table_lookup (save_descriptor->replacements, ids[i]);
220
if (G_UNLIKELY (replacement_id != NULL))
223
ids[i] = g_strdup (replacement_id);
226
/* check if this id was already present on the list */
227
for (k = 0; k < j; ++k)
228
if (strcmp (ids[k], ids[i]) == 0)
231
/* we don't need to keep it, if it was already present */
232
if (G_UNLIKELY (k < j))
236
else if (!check_desktop_id (ids[i]))
238
/* we also don't keep references to dead desktop-ids around */
248
/* null-terminate the list */
251
/* no need to store an empty list */
252
if (G_LIKELY (ids[0] != NULL))
254
fprintf (save_descriptor->fp, "%s=%s", type, ids[0]);
255
for (i = 1; ids[i] != NULL; ++i)
256
fprintf (save_descriptor->fp, ";%s", ids[i]);
257
fprintf (save_descriptor->fp, "\n");
264
save_defaults_list (const gchar *directory,
265
GHashTable *defaults_list,
266
GHashTable *replacements)
268
SaveDescriptor save_descriptor;
269
gchar *defaults_list_path;
274
/* determine the path to the defaults.list */
275
defaults_list_path = g_build_filename (directory, "defaults.list", NULL);
277
/* write the default applications list to a temporary file */
278
path = g_strdup_printf ("%s.XXXXXX", defaults_list_path);
279
fd = g_mkstemp (path);
280
if (G_LIKELY (fd >= 0))
282
/* wrap the descriptor in a file pointer */
283
fp = fdopen (fd, "w");
285
/* initialize the save descriptor */
286
save_descriptor.replacements = replacements;
287
save_descriptor.fp = fp;
289
/* write the default application list content */
290
fprintf (fp, "[Default Applications]\n");
291
g_hash_table_foreach (defaults_list, (GHFunc) save_defaults_list_one, &save_descriptor);
294
/* try to atomically rename the file */
295
if (G_UNLIKELY (g_rename (path, defaults_list_path) < 0))
297
/* tell the user that we failed */
298
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to write defaults.list: %s\n", g_strerror (errno));
300
/* be sure to remove the temporary file */
306
g_free (defaults_list_path);
313
main (int argc, char **argv)
315
const gchar *other_name;
320
GHashTable *defaults_list;
321
GHashTable *replacements;
322
GHashTable *usercreated;
326
GError *error = NULL;
330
GList *obsolete = NULL;
334
/* determine the $XDG_DATA_HOME/applications directory */
335
directory = xfce_resource_save_location (XFCE_RESOURCE_DATA, "applications/", TRUE);
336
if (G_UNLIKELY (directory == NULL))
338
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to determine the user's applications directory.\n");
342
/* verify that we can enter that directory */
343
if (G_UNLIKELY (chdir (directory) < 0))
345
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to enter directory %s: %s\n", directory, g_strerror (errno));
349
/* compile the regular expression to match usercreated .desktop files */
350
if (regcomp (®ex, "^.*-usercreated(-[0-9]+)?\\.desktop", REG_EXTENDED) < 0)
352
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to compile regular expression: %s\n", g_strerror (errno));
356
/* try to open the new current directory */
357
dp = g_dir_open (".", 0, &error);
358
if (G_UNLIKELY (dp == NULL))
360
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to open directory %s: %s\n", directory, error->message);
361
g_error_free (error);
365
/* load the defaults.list file */
366
defaults_list = load_defaults_list (directory);
368
/* allocate a hash table used to combine the <app>-usercreated.desktop files */
369
usercreated = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
371
/* allocate a hash table for the replacements (old.desktop -> new.desktop) */
372
replacements = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
374
/* process the directory contents, collecting obsolete files */
377
/* determine the next file name */
378
name = g_dir_read_name (dp);
379
if (G_UNLIKELY (name == NULL))
382
/* check if the file name matches */
383
if (regexec (®ex, name, 0, NULL, 0) != 0)
386
/* try to open that file */
387
rc = xfce_rc_simple_open (name, TRUE);
388
if (G_UNLIKELY (rc == NULL))
391
/* we care only for the [Desktop Entry] group */
392
xfce_rc_set_group (rc, "Desktop Entry");
394
/* determine the "Exec" value */
395
exec = xfce_rc_read_entry_untranslated (rc, "Exec", NULL);
396
if (G_LIKELY (!check_exec (exec)))
398
/* this one is obsolete */
399
obsolete = g_list_append (obsolete, g_strdup (name));
401
else if (G_LIKELY (exec != NULL))
403
/* check if already present in the usercreated hash table */
404
other_name = g_hash_table_lookup (usercreated, exec);
405
other_rc = (other_name != NULL) ? xfce_rc_simple_open (other_name, FALSE) : NULL;
406
if (G_UNLIKELY (other_rc != NULL))
408
/* two files with the same "Exec" line, we can combine them by merging the MimeType */
409
mt1 = xfce_rc_read_entry_untranslated (other_rc, "MimeType", NULL);
410
mt2 = xfce_rc_read_entry_untranslated (rc, "MimeType", NULL);
412
/* combine the MimeTypes to a single list */
413
if (G_LIKELY (mt1 != NULL && mt1[strlen (mt1) - 1] == ';'))
414
mt = g_strconcat (mt1, mt2, NULL);
415
else if (G_LIKELY (mt1 != NULL))
416
mt = g_strconcat (mt1, ";", mt2, NULL);
420
/* store the new entry (if any) to the other file */
421
if (G_LIKELY (mt != NULL))
423
xfce_rc_write_entry (other_rc, "MimeType", mt);
427
/* remember the replacement for later */
428
g_hash_table_insert (replacements, g_strdup (name), g_strdup (other_name));
430
/* no need to keep the new file around, now that we merged the stuff */
431
if (g_unlink (name) < 0 && errno != ENOENT)
432
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to unlink %s: %s\n", name, g_strerror (errno));
434
/* close the other rc file */
435
xfce_rc_close (other_rc);
439
/* just add this one to the usercreated hash table */
440
g_hash_table_insert (usercreated, g_strdup (exec), g_strdup (name));
448
/* write a defaults.list, replacing merged desktop-ids and dropping dead ones */
449
save_defaults_list (directory, defaults_list, replacements);
451
/* release the regular expression */
454
/* close the directory handle */
457
/* check if we have any obsolete files */
458
if (G_UNLIKELY (obsolete != NULL))
460
/* remove all obsolete files from the directory */
461
for (lp = obsolete; lp != NULL; lp = lp->next)
463
/* try to remove the file */
464
if (G_UNLIKELY (g_unlink (lp->data) < 0 && errno != ENOENT))
466
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to unlink %s: %s\n", (gchar *) lp->data, g_strerror (errno));
471
g_list_free (obsolete);
473
/* run update-desktop-database on the applications folder */
474
command = g_strdup_printf ("update-desktop-database %s", directory);
475
if (!g_spawn_command_line_sync (command, NULL, NULL, NULL, &error))
477
fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to execute %s: %s\n", command, error->message);
478
g_error_free (error);
485
g_hash_table_destroy (defaults_list);
486
g_hash_table_destroy (replacements);
487
g_hash_table_destroy (usercreated);