~noskcaj/ubuntu/vivid/thunar/1.6.4

« back to all changes in this revision

Viewing changes to thunar-vfs/thunar-vfs-mime-cleaner.c

  • Committer: Bazaar Package Importer
  • Author(s): Lionel Le Folgoc
  • Date: 2010-12-04 16:46:20 UTC
  • mto: (2.1.3 experimental) (1.3.1)
  • mto: This revision was merged to the branch mainline in revision 69.
  • Revision ID: james.westby@ubuntu.com-20101204164620-h7p4t2e9z6hfhz6l
Tags: upstream-1.1.4
ImportĀ upstreamĀ versionĀ 1.1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* $Id$ */
2
 
/*-
3
 
 * Copyright (c) 2006 Benedikt Meurer <benny@xfce.org>
4
 
 *
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)
8
 
 * any later version.
9
 
 *
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
13
 
 * more details.
14
 
 *
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
18
 
 */
19
 
 
20
 
/********************************************************************************
21
 
 * WHAT IS THIS?                                                                *
22
 
 *                                                                              *
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.                                                 *
29
 
 *                                                                              *
30
 
 * In addition, the defaults.list file will be cleaned and references to dead   *
31
 
 * desktop-ids will be removed from the list.                                   *
32
 
 ********************************************************************************/
33
 
 
34
 
#ifdef HAVE_CONFIG_H
35
 
#include <config.h>
36
 
#endif
37
 
 
38
 
#ifdef HAVE_SYS_TYPES_H
39
 
#include <sys/types.h>
40
 
#endif
41
 
 
42
 
#ifdef HAVE_ERRNO_H
43
 
#include <errno.h>
44
 
#endif
45
 
#ifdef HAVE_MEMORY_H
46
 
#include <memory.h>
47
 
#endif
48
 
#ifdef HAVE_REGEX_H
49
 
#include <regex.h>
50
 
#endif
51
 
#include <stdio.h>
52
 
#ifdef HAVE_STDLIB_H
53
 
#include <stdlib.h>
54
 
#endif
55
 
#ifdef HAVE_STRING_H
56
 
#include <string.h>
57
 
#endif
58
 
#ifdef HAVE_UNISTD_H
59
 
#include <unistd.h>
60
 
#endif
61
 
 
62
 
#include <glib/gstdio.h>
63
 
#include <libxfce4util/libxfce4util.h>
64
 
 
65
 
 
66
 
 
67
 
static gboolean
68
 
check_exec (const gchar *exec)
69
 
{
70
 
  gboolean result = TRUE;
71
 
  gchar   *command;
72
 
  gchar  **argv;
73
 
 
74
 
  /* exec = NULL is invalid */
75
 
  if (G_UNLIKELY (exec == NULL))
76
 
    return FALSE;
77
 
 
78
 
  /* try to parse the "Exec" line */
79
 
  if (g_shell_parse_argv (exec, NULL, &argv, NULL))
80
 
    {
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));
83
 
 
84
 
      /* otherwise, argv[0] must be a program in $PATH */
85
 
      if (G_LIKELY (!result))
86
 
        {
87
 
          /* search in $PATH for argv[0] */
88
 
          command = g_find_program_in_path (argv[0]);
89
 
          result = (command != NULL);
90
 
          g_free (command);
91
 
        }
92
 
 
93
 
      /* release the argv */
94
 
      g_strfreev (argv);
95
 
    }
96
 
 
97
 
  return result;
98
 
}
99
 
 
100
 
 
101
 
 
102
 
static GHashTable*
103
 
load_defaults_list (const gchar *directory)
104
 
{
105
 
  const gchar *type;
106
 
  const gchar *ids;
107
 
  GHashTable  *defaults_list;
108
 
  gchar      **id_list;
109
 
  gchar        line[2048];
110
 
  gchar       *path;
111
 
  gchar       *lp;
112
 
  FILE        *fp;
113
 
  gint         n;
114
 
  gint         m;
115
 
 
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);
118
 
 
119
 
  /* determine the path to the defaults.list */
120
 
  path = g_build_filename (directory, "defaults.list", NULL);
121
 
 
122
 
  /* try to open the defaults.list file */
123
 
  fp = fopen (path, "r");
124
 
  if (G_LIKELY (fp != NULL))
125
 
    {
126
 
      /* process the file */
127
 
      while (fgets (line, sizeof (line), fp) != NULL)
128
 
        {
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'))
132
 
            continue;
133
 
 
134
 
          /* lookup the '=' */
135
 
          for (type = lp++; *lp != '\0' && *lp != '='; ++lp) ;
136
 
          if (G_UNLIKELY (*lp != '='))
137
 
            continue;
138
 
          *lp++ = '\0';
139
 
 
140
 
          /* extract the application id list */
141
 
          for (ids = lp; g_ascii_isgraph (*lp); ++lp) ;
142
 
          if (G_UNLIKELY (ids == lp))
143
 
            continue;
144
 
          *lp = '\0';
145
 
 
146
 
          /* parse the id list */
147
 
          id_list = g_strsplit (ids, ";", -1);
148
 
          for (m = n = 0; id_list[m] != NULL; ++m)
149
 
            {
150
 
              if (G_UNLIKELY (id_list[m][0] == '\0'))
151
 
                g_free (id_list[m]);
152
 
              else
153
 
                id_list[n++] = id_list[m];
154
 
            }
155
 
          id_list[n] = NULL;
156
 
 
157
 
          /* verify that the id list is not empty */
158
 
          if (G_UNLIKELY (id_list[0] == NULL))
159
 
            {
160
 
              g_free (id_list);
161
 
              continue;
162
 
            }
163
 
 
164
 
          /* insert the line into the hash table */
165
 
          g_hash_table_insert (defaults_list, g_strdup (type), id_list);
166
 
        }
167
 
 
168
 
      /* close the file */
169
 
      fclose (fp);
170
 
    }
171
 
 
172
 
  /* cleanup */
173
 
  g_free (path);
174
 
 
175
 
  return defaults_list;
176
 
}
177
 
 
178
 
 
179
 
 
180
 
static gboolean
181
 
check_desktop_id (const gchar *id)
182
 
{
183
 
  GKeyFile *key_file;
184
 
  gboolean  result;
185
 
  gchar    *path;
186
 
 
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);
192
 
  g_free (path);
193
 
 
194
 
  return result;
195
 
}
196
 
 
197
 
 
198
 
 
199
 
typedef struct
200
 
{
201
 
  GHashTable *replacements;
202
 
  FILE       *fp;
203
 
} SaveDescriptor;
204
 
 
205
 
 
206
 
 
207
 
static void
208
 
save_defaults_list_one (const gchar    *type,
209
 
                        gchar         **ids,
210
 
                        SaveDescriptor *save_descriptor)
211
 
{
212
 
  const gchar *replacement_id;
213
 
  gint         i, j, k;
214
 
 
215
 
  /* perform the required transformations on the id list */
216
 
  for (i = j = 0; ids[i] != NULL; ++i)
217
 
    {
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))
221
 
        {
222
 
          g_free (ids[i]);
223
 
          ids[i] = g_strdup (replacement_id);
224
 
        }
225
 
 
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)
229
 
          break;
230
 
 
231
 
      /* we don't need to keep it, if it was already present */
232
 
      if (G_UNLIKELY (k < j))
233
 
        {
234
 
          g_free (ids[i]);
235
 
        }
236
 
      else if (!check_desktop_id (ids[i]))
237
 
        {
238
 
          /* we also don't keep references to dead desktop-ids around */
239
 
          g_free (ids[i]);
240
 
        }
241
 
      else
242
 
        {
243
 
          /* keep it around */
244
 
          ids[j++] = ids[i];
245
 
        }
246
 
    }
247
 
 
248
 
  /* null-terminate the list */
249
 
  ids[j] = NULL;
250
 
 
251
 
  /* no need to store an empty list */
252
 
  if (G_LIKELY (ids[0] != NULL))
253
 
    {
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");
258
 
    }
259
 
}
260
 
 
261
 
 
262
 
 
263
 
static void
264
 
save_defaults_list (const gchar *directory,
265
 
                    GHashTable  *defaults_list,
266
 
                    GHashTable  *replacements)
267
 
{
268
 
  SaveDescriptor save_descriptor;
269
 
  gchar         *defaults_list_path;
270
 
  gchar         *path;
271
 
  FILE          *fp;
272
 
  gint           fd;
273
 
 
274
 
  /* determine the path to the defaults.list */
275
 
  defaults_list_path = g_build_filename (directory, "defaults.list", NULL);
276
 
 
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))
281
 
    {
282
 
      /* wrap the descriptor in a file pointer */
283
 
      fp = fdopen (fd, "w");
284
 
 
285
 
      /* initialize the save descriptor */
286
 
      save_descriptor.replacements = replacements;
287
 
      save_descriptor.fp = fp;
288
 
 
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);
292
 
      fclose (fp);
293
 
 
294
 
      /* try to atomically rename the file */
295
 
      if (G_UNLIKELY (g_rename (path, defaults_list_path) < 0))
296
 
        {
297
 
          /* tell the user that we failed */
298
 
          fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to write defaults.list: %s\n", g_strerror (errno));
299
 
 
300
 
          /* be sure to remove the temporary file */
301
 
          g_unlink (path);
302
 
        }
303
 
    }
304
 
 
305
 
  /* cleanup */
306
 
  g_free (defaults_list_path);
307
 
  g_free (path);
308
 
}
309
 
 
310
 
 
311
 
 
312
 
int
313
 
main (int argc, char **argv)
314
 
{
315
 
  const gchar *other_name;
316
 
  const gchar *exec;
317
 
  const gchar *name;
318
 
  const gchar *mt1;
319
 
  const gchar *mt2;
320
 
  GHashTable  *defaults_list;
321
 
  GHashTable  *replacements;
322
 
  GHashTable  *usercreated;
323
 
  regex_t      regex;
324
 
  XfceRc      *other_rc;
325
 
  XfceRc      *rc;
326
 
  GError      *error = NULL;
327
 
  gchar       *directory;
328
 
  gchar       *command;
329
 
  gchar       *mt;
330
 
  GList       *obsolete = NULL;
331
 
  GList       *lp;
332
 
  GDir        *dp;
333
 
 
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))
337
 
    {
338
 
      fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to determine the user's applications directory.\n");
339
 
      return EXIT_FAILURE;
340
 
    }
341
 
 
342
 
  /* verify that we can enter that directory */
343
 
  if (G_UNLIKELY (chdir (directory) < 0))
344
 
    {
345
 
      fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to enter directory %s: %s\n", directory, g_strerror (errno));
346
 
      return EXIT_FAILURE;
347
 
    }
348
 
 
349
 
  /* compile the regular expression to match usercreated .desktop files */
350
 
  if (regcomp (&regex, "^.*-usercreated(-[0-9]+)?\\.desktop", REG_EXTENDED) < 0)
351
 
    {
352
 
      fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to compile regular expression: %s\n", g_strerror (errno));
353
 
      return EXIT_FAILURE;
354
 
    }
355
 
 
356
 
  /* try to open the new current directory */
357
 
  dp = g_dir_open (".", 0, &error);
358
 
  if (G_UNLIKELY (dp == NULL))
359
 
    {
360
 
      fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to open directory %s: %s\n", directory, error->message);
361
 
      g_error_free (error);
362
 
      return EXIT_FAILURE;
363
 
    }
364
 
 
365
 
  /* load the defaults.list file */
366
 
  defaults_list = load_defaults_list (directory);
367
 
 
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);
370
 
 
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);
373
 
 
374
 
  /* process the directory contents, collecting obsolete files */
375
 
  for (;;)
376
 
    {
377
 
      /* determine the next file name */
378
 
      name = g_dir_read_name (dp);
379
 
      if (G_UNLIKELY (name == NULL))
380
 
        break;
381
 
 
382
 
      /* check if the file name matches */
383
 
      if (regexec (&regex, name, 0, NULL, 0) != 0)
384
 
        continue;
385
 
 
386
 
      /* try to open that file */
387
 
      rc = xfce_rc_simple_open (name, TRUE);
388
 
      if (G_UNLIKELY (rc == NULL))
389
 
        continue;
390
 
 
391
 
      /* we care only for the [Desktop Entry] group */
392
 
      xfce_rc_set_group (rc, "Desktop Entry");
393
 
 
394
 
      /* determine the "Exec" value */
395
 
      exec = xfce_rc_read_entry_untranslated (rc, "Exec", NULL);
396
 
      if (G_LIKELY (!check_exec (exec)))
397
 
        {
398
 
          /* this one is obsolete */
399
 
          obsolete = g_list_append (obsolete, g_strdup (name));
400
 
        }
401
 
      else if (G_LIKELY (exec != NULL))
402
 
        {
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))
407
 
            {
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);
411
 
 
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);
417
 
              else
418
 
                mt = g_strdup (mt2);
419
 
 
420
 
              /* store the new entry (if any) to the other file */
421
 
              if (G_LIKELY (mt != NULL))
422
 
                {
423
 
                  xfce_rc_write_entry (other_rc, "MimeType", mt);
424
 
                  g_free (mt);
425
 
                }
426
 
 
427
 
              /* remember the replacement for later */
428
 
              g_hash_table_insert (replacements, g_strdup (name), g_strdup (other_name));
429
 
 
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));
433
 
 
434
 
              /* close the other rc file */
435
 
              xfce_rc_close (other_rc);
436
 
            }
437
 
          else
438
 
            {
439
 
              /* just add this one to the usercreated hash table */
440
 
              g_hash_table_insert (usercreated, g_strdup (exec), g_strdup (name));
441
 
            }
442
 
        }
443
 
 
444
 
      /* close the file */
445
 
      xfce_rc_close (rc);
446
 
    }
447
 
 
448
 
  /* write a defaults.list, replacing merged desktop-ids and dropping dead ones */
449
 
  save_defaults_list (directory, defaults_list, replacements);
450
 
 
451
 
  /* release the regular expression */
452
 
  regfree (&regex);
453
 
 
454
 
  /* close the directory handle */
455
 
  g_dir_close (dp);
456
 
 
457
 
  /* check if we have any obsolete files */
458
 
  if (G_UNLIKELY (obsolete != NULL))
459
 
    {
460
 
      /* remove all obsolete files from the directory */
461
 
      for (lp = obsolete; lp != NULL; lp = lp->next)
462
 
        {
463
 
          /* try to remove the file */
464
 
          if (G_UNLIKELY (g_unlink (lp->data) < 0 && errno != ENOENT))
465
 
            {
466
 
              fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to unlink %s: %s\n", (gchar *) lp->data, g_strerror (errno));
467
 
              return EXIT_FAILURE;
468
 
            }
469
 
          g_free (lp->data);
470
 
        }
471
 
      g_list_free (obsolete);
472
 
 
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))
476
 
        {
477
 
          fprintf (stderr, "thunar-vfs-mime-cleaner: Failed to execute %s: %s\n", command, error->message);
478
 
          g_error_free (error);
479
 
          return EXIT_FAILURE;
480
 
        }
481
 
      g_free (command);
482
 
    }
483
 
 
484
 
  /* cleanup */
485
 
  g_hash_table_destroy (defaults_list);
486
 
  g_hash_table_destroy (replacements);
487
 
  g_hash_table_destroy (usercreated);
488
 
  g_free (directory);
489
 
 
490
 
  return EXIT_SUCCESS;
491
 
}