~ubuntu-branches/ubuntu/trusty/pcmanfm/trusty-proposed

« back to all changes in this revision

Viewing changes to src/mime-type/mime-action.c

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Lee
  • Date: 2008-09-26 10:19:20 UTC
  • mfrom: (4.1.5 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080926101920-cfldybkmwgwrtv9u
Tags: 0.5-3
* Correct spellings,  03_correct_spelling.dpatch (Closes:498794) 
* Code in some files are taken from other projects, added these
  informations into copyright file. (Closes:499678)
* Applied 04_defaut_terminal.dpatch to support x-terminal-emulator
  alternative. (Closes:497494) 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *      mime-action.c
 
3
 *
 
4
 *      Copyright 2007 PCMan <pcman.tw@gmail.com>
 
5
 *
 
6
 *      This program is free software; you can redistribute it and/or modify
 
7
 *      it under the terms of the GNU General Public License as published by
 
8
 *      the Free Software Foundation; either version 2 of the License, or
 
9
 *      (at your option) any later version.
 
10
 *
 
11
 *      This program is distributed in the hope that it will be useful,
 
12
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 *      GNU General Public License for more details.
 
15
 *
 
16
 *      You should have received a copy of the GNU General Public License
 
17
 *      along with this program; if not, write to the Free Software
 
18
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
19
 *      MA 02110-1301, USA.
 
20
 */
 
21
 
 
22
#ifdef HAVE_CONFIG_H
 
23
#include <config.h>
 
24
#endif
 
25
 
 
26
#include "mime-action.h"
 
27
#include "glib-utils.h"
 
28
#include <string.h>
 
29
#include <unistd.h>
 
30
#include <sys/types.h>
 
31
#include <sys/stat.h>
 
32
#include <fcntl.h>
 
33
 
 
34
gboolean save_to_file( const char* path, const char* data, gssize len )
 
35
{
 
36
    int fd = creat( path, 0644 );
 
37
    if( fd == -1 )
 
38
        return FALSE;
 
39
 
 
40
    if( write( fd, data, len ) == -1 )
 
41
    {
 
42
        close( fd );
 
43
        return FALSE;
 
44
    }
 
45
    close( fd );
 
46
    return TRUE;
 
47
}
 
48
 
 
49
const char group_desktop[] = "Desktop Entry";
 
50
const char key_mime_type[] = "MimeType";
 
51
 
 
52
typedef char* (*DataDirFunc)    ( const char* dir, const char* mime_type, gpointer user_data );
 
53
 
 
54
static char* data_dir_foreach( DataDirFunc func, const char* mime_type, gpointer user_data )
 
55
{
 
56
    char* ret = NULL;
 
57
    const gchar* const * dirs;
 
58
    const char* dir = g_get_user_data_dir();
 
59
 
 
60
    if( (ret = func( dir, mime_type, user_data )) )
 
61
        return ret;
 
62
 
 
63
    dirs = g_get_system_data_dirs();
 
64
    for( ; *dirs; ++dirs )
 
65
    {
 
66
        if( (ret = func( *dirs, mime_type, user_data )) )
 
67
            return ret;
 
68
    }
 
69
    return NULL;
 
70
}
 
71
 
 
72
static void update_desktop_database()
 
73
{
 
74
    char* argv[3];
 
75
    argv[0] = g_find_program_in_path( "update-desktop-database" );
 
76
    if( G_UNLIKELY( ! argv[0] ) )
 
77
        return;
 
78
    argv[1] = g_build_filename( g_get_user_data_dir(), "applications", NULL );
 
79
    argv[2] = NULL;
 
80
    g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
 
81
    g_free( argv[0] );
 
82
    g_free( argv[1] );
 
83
}
 
84
 
 
85
static int strv_index( char** strv, const char* str )
 
86
{
 
87
    char**p;
 
88
    if( G_LIKELY( strv && str ) )
 
89
    {
 
90
        for( p = strv; *p; ++p )
 
91
        {
 
92
            if( 0 == strcmp( *p, str ) )
 
93
                return (p - strv);
 
94
        }
 
95
    }
 
96
    return -1;
 
97
}
 
98
 
 
99
static char* get_actions( const char* dir, const char* type, GArray* actions )
 
100
{
 
101
    GKeyFile* file;
 
102
    gboolean opened;
 
103
    char** apps = NULL;
 
104
    char* path = g_build_filename( dir, "applications/mimeinfo.cache", NULL );
 
105
    file = g_key_file_new();
 
106
    opened = g_key_file_load_from_file( file, path, 0, NULL );
 
107
    g_free( path );
 
108
    if( G_LIKELY( opened ) )
 
109
    {
 
110
        gsize n_apps = 0, i;
 
111
        apps = g_key_file_get_string_list( file, "MIME Cache", type, &n_apps, NULL );
 
112
        for( i = 0; i < n_apps; ++i )
 
113
        {
 
114
            if( -1 == strv_index( (char**)actions->data, apps[i] ) )
 
115
            {
 
116
                /* check for existence */
 
117
                path = mime_type_locate_desktop_file( dir, apps[i] );
 
118
                if( G_LIKELY(path) )
 
119
                {
 
120
                    g_array_append_val( actions, apps[i] );
 
121
                    g_free( path );
 
122
                }
 
123
                else
 
124
                    g_free( apps[i] );
 
125
                apps[i] = NULL; /* steal the string */
 
126
            }
 
127
            else
 
128
            {
 
129
                g_free( apps[i] );
 
130
                apps[i] = NULL;
 
131
            }
 
132
        }
 
133
        g_free( apps ); /* don't call g_strfreev since all strings in the array was stolen. */
 
134
    }
 
135
    g_key_file_free( file );
 
136
    return NULL;    /* return NULL so the for_each operation doesn't stop. */
 
137
}
 
138
 
 
139
/*
 
140
 *  Get a list of applications supporting this mime-type
 
141
 * The returned string array was newly allocated, and should be
 
142
 * freed with g_strfreev() when no longer used.
 
143
 */
 
144
char** mime_type_get_actions( const char* type )
 
145
{
 
146
    GArray* actions = g_array_sized_new( TRUE, FALSE, sizeof(char*), 10 );
 
147
    char* default_app = NULL;
 
148
 
 
149
    /* FIXME: actions of parent types should be added, too. */
 
150
 
 
151
    /* get all actions for this file type */
 
152
    data_dir_foreach( (DataDirFunc)get_actions, type, actions );
 
153
 
 
154
    /* ensure default app is in the list */
 
155
    if( G_LIKELY( ( default_app = mime_type_get_default_action( type ) ) ) )
 
156
    {
 
157
        int i = strv_index( (char**)actions->data, default_app );
 
158
        if( i == -1 )   /* default app is not in the list, add it! */
 
159
        {
 
160
            g_array_prepend_val( actions, default_app );
 
161
        }
 
162
        else  /* default app is in the list, move it to the first. */
 
163
        {
 
164
            if( i != 0 )
 
165
            {
 
166
                char** pdata = (char**)actions->data;
 
167
                char* tmp = pdata[i];
 
168
                g_array_remove_index( actions, i );
 
169
                g_array_prepend_val( actions, tmp );
 
170
            }
 
171
            g_free( default_app );
 
172
        }
 
173
    }
 
174
    return (char**)g_array_free( actions, actions->len == 0 );
 
175
}
 
176
 
 
177
/*
 
178
 * NOTE:
 
179
 * This API is very time consuming, but unfortunately, due to the damn poor design of
 
180
 * Freedesktop.org spec, all the insane checks here are necessary.  Sigh...  :-(
 
181
 */
 
182
gboolean mime_type_has_action( const char* type, const char* desktop_id )
 
183
{
 
184
    char** actions, **action;
 
185
    char *cmd = NULL, *name = NULL;
 
186
    gboolean found = FALSE;
 
187
    gboolean is_desktop = g_str_has_suffix( desktop_id, ".desktop" );
 
188
 
 
189
    if( is_desktop )
 
190
    {
 
191
        char** types;
 
192
        GKeyFile* kf = g_key_file_new();
 
193
        char* filename = mime_type_locate_desktop_file( NULL, desktop_id );
 
194
        if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
 
195
        {
 
196
            types = g_key_file_get_string_list( kf, group_desktop, key_mime_type, NULL, NULL );
 
197
            if( -1 != strv_index( types, type ) )
 
198
            {
 
199
                /* our mime-type is already found in the desktop file. no further check is needed */
 
200
                found = TRUE;
 
201
            }
 
202
            g_strfreev( types );
 
203
 
 
204
            if( ! found )   /* get the content of desktop file for comparison */
 
205
            {
 
206
                cmd = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
 
207
                name = g_key_file_get_string( kf, group_desktop, "Name", NULL );
 
208
            }
 
209
        }
 
210
        g_free( filename );
 
211
        g_key_file_free( kf );
 
212
    }
 
213
    else
 
214
    {
 
215
        cmd = (char*)desktop_id;
 
216
    }
 
217
 
 
218
    actions = mime_type_get_actions( type );
 
219
    if( actions )
 
220
    {
 
221
        for( action = actions; ! found && *action; ++action )
 
222
        {
 
223
            /* Try to match directly by desktop_id first */
 
224
            if( is_desktop && 0 == strcmp( *action, desktop_id ) )
 
225
            {
 
226
                found = TRUE;
 
227
            }
 
228
            else /* Then, try to match by "Exec" and "Name" keys */
 
229
            {
 
230
                char *name2 = NULL, *cmd2 = NULL, *filename = NULL;
 
231
                GKeyFile* kf = g_key_file_new();
 
232
                filename = mime_type_locate_desktop_file( NULL, *action );
 
233
                if( filename && g_key_file_load_from_file( kf, filename, 0, NULL ) )
 
234
                {
 
235
                    cmd2 = g_key_file_get_string( kf, group_desktop, "Exec", NULL );
 
236
                    if( cmd && cmd2 && 0 == strcmp( cmd, cmd2 ) )   /* 2 desktop files have same "Exec" */
 
237
                    {
 
238
                        if( is_desktop )
 
239
                        {
 
240
                            name2 = g_key_file_get_string( kf, group_desktop, "Name", NULL );
 
241
                            /* Then, check if the "Name" keys of 2 desktop files are the same. */
 
242
                            if( name && name2 && 0 == strcmp( name, name2 ) )
 
243
                            {
 
244
                                /* Both "Exec" and "Name" keys of the 2 desktop files are
 
245
                                 *  totally the same. So, despite having different desktop id
 
246
                                 *  They actually refer to the same application. */
 
247
                                found = TRUE;
 
248
                            }
 
249
                            g_free( name2 );
 
250
                        }
 
251
                        else
 
252
                            found = TRUE;
 
253
                    }
 
254
                }
 
255
                g_free( filename );
 
256
                g_free( cmd2 );
 
257
                g_key_file_free( kf );
 
258
            }
 
259
        }
 
260
        g_strfreev( actions );
 
261
    }
 
262
    if( is_desktop )
 
263
    {
 
264
        g_free( cmd );
 
265
        g_free( name );
 
266
    }
 
267
    return found;
 
268
}
 
269
 
 
270
#if 0
 
271
static gboolean is_custom_desktop_file( const char* desktop_id )
 
272
{
 
273
    char* path = g_build_filename( g_get_user_data_dir(), "applications", desktop_id, NULL );
 
274
    gboolean ret = g_file_test( path, G_FILE_TEST_EXISTS );
 
275
    g_free( path );
 
276
    return ret;
 
277
}
 
278
#endif
 
279
 
 
280
static char* make_custom_desktop_file( const char* desktop_id, const char* mime_type )
 
281
{
 
282
    char *name = NULL, *cust_template = NULL, *cust = NULL, *path, *dir;
 
283
    char* file_content = NULL;
 
284
    gsize len = 0;
 
285
    guint i;
 
286
 
 
287
    if( G_LIKELY( g_str_has_suffix(desktop_id, ".desktop") ) )
 
288
    {
 
289
        GKeyFile *kf = g_key_file_new();
 
290
        char* name = mime_type_locate_desktop_file( NULL, desktop_id );
 
291
        if( G_UNLIKELY( ! name || ! g_key_file_load_from_file( kf, name,
 
292
                                                            G_KEY_FILE_KEEP_TRANSLATIONS, NULL ) ) )
 
293
        {
 
294
            g_free( name );
 
295
            return NULL; /* not a valid desktop file */
 
296
        }
 
297
        g_free( name );
 
298
/*
 
299
        FIXME: If the source desktop_id refers to a custom desktop file, and
 
300
                    value of the MimeType key equals to our mime-type, there is no
 
301
                    need to generate a new desktop file.
 
302
        if( G_UNLIKELY( is_custom_desktop_file( desktop_id ) ) )
 
303
        {
 
304
        }
 
305
*/
 
306
        /* set our mime-type */
 
307
        g_key_file_set_string_list( kf, group_desktop, key_mime_type, &mime_type, 1 );
 
308
        /* store id of original desktop file, for future use. */
 
309
        g_key_file_set_string( kf, group_desktop, "X-MimeType-Derived", desktop_id );
 
310
        g_key_file_set_string( kf, group_desktop, "NoDisplay", "true" );
 
311
 
 
312
        name = g_strndup( desktop_id, strlen(desktop_id) - 8 );
 
313
        cust_template = g_strdup_printf( "%s-usercustom-%%d.desktop", name );
 
314
        g_free( name );
 
315
 
 
316
        file_content = g_key_file_to_data( kf, &len, NULL );
 
317
        g_key_file_free( kf );
 
318
    }
 
319
   else  /* it's not a desktop_id, but a command */
 
320
    {
 
321
        char* p;
 
322
        const char file_templ[] =
 
323
            "[Desktop Entry]\n"
 
324
            "Encoding=UTF-8\n"
 
325
            "Name=%s\n"
 
326
            "Exec=%s\n"
 
327
            "MimeType=%s\n"
 
328
            "Icon=exec\n"
 
329
            "NoDisplay=true\n"; /* FIXME: Terminal? */
 
330
        /* Make a user-created desktop file for the command */
 
331
        name = g_path_get_basename( desktop_id );
 
332
        if( (p = strchr(name, ' ')) ) /* FIXME: skip command line arguments. is this safe? */
 
333
            *p = '\0';
 
334
        file_content = g_strdup_printf( file_templ, name, desktop_id, mime_type );
 
335
        len = strlen( file_content );
 
336
        cust_template = g_strdup_printf( "%s-usercreated-%%d.desktop", name );
 
337
        g_free( name );
 
338
    }
 
339
 
 
340
    /* generate unique file name */
 
341
    dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
 
342
    g_mkdir_with_parents( dir, 0700 );
 
343
    for( i = 0; ; ++i )
 
344
    {
 
345
        /* generate the basename */
 
346
        cust = g_strdup_printf( cust_template, i );
 
347
        path = g_build_filename( dir, cust, NULL ); /* test if the filename already exists */
 
348
        if( g_file_test( path, G_FILE_TEST_EXISTS ) )
 
349
        {
 
350
            g_free( cust );
 
351
            g_free( path );
 
352
        }
 
353
        else /* this generated filename can be used */
 
354
            break;
 
355
    }
 
356
    g_free( dir );
 
357
    if( G_LIKELY( path ) )
 
358
    {
 
359
        save_to_file( path, file_content, len );
 
360
        g_free( path );
 
361
 
 
362
        /* execute update-desktop-database" to update mimeinfo.cache */
 
363
        update_desktop_database();
 
364
    }
 
365
    return cust;
 
366
}
 
367
 
 
368
/*
 
369
 * Add an applications used to open this mime-type
 
370
 * desktop_id is the name of *.desktop file.
 
371
 *
 
372
 * custom_desktop: used to store name of the newly created user-custom desktop file, can be NULL.
 
373
 */
 
374
void mime_type_add_action( const char* type, const char* desktop_id, char** custom_desktop )
 
375
{
 
376
    char* cust;
 
377
    if( mime_type_has_action( type, desktop_id ) )
 
378
    {
 
379
        if( custom_desktop )
 
380
            *custom_desktop = g_strdup( desktop_id );
 
381
        return;
 
382
    }
 
383
 
 
384
    cust = make_custom_desktop_file( desktop_id, type );
 
385
    if( custom_desktop )
 
386
        *custom_desktop = cust;
 
387
    else
 
388
        g_free( cust );
 
389
}
 
390
 
 
391
static char* _locate_desktop_file( const char* dir, const char* unused, const gpointer desktop_id )
 
392
{
 
393
    char *path, *sep = NULL;
 
394
    gboolean found = FALSE;
 
395
 
 
396
    path = g_build_filename( dir, "applications", (const char*)desktop_id, NULL );
 
397
    sep = strchr( (const char*)desktop_id, '-' );
 
398
    if( sep )
 
399
        sep = strrchr( path, '-' );
 
400
 
 
401
    do{
 
402
        if( g_file_test( path, G_FILE_TEST_IS_REGULAR ) )
 
403
        {
 
404
            found = TRUE;
 
405
            break;
 
406
        }
 
407
        if( sep )
 
408
        {
 
409
            *sep = '/';
 
410
            sep = strchr( sep + 1, '-' );
 
411
        }
 
412
        else
 
413
            break;
 
414
    }while( ! found );
 
415
 
 
416
    if( ! found )
 
417
    {
 
418
        g_free( path );
 
419
        return NULL;
 
420
    }
 
421
    return path;
 
422
}
 
423
 
 
424
char* mime_type_locate_desktop_file( const char* dir, const char* desktop_id )
 
425
{
 
426
    if( dir )
 
427
        return _locate_desktop_file( dir, NULL, (gpointer) desktop_id );
 
428
    return data_dir_foreach( _locate_desktop_file, NULL, (gpointer) desktop_id );
 
429
}
 
430
 
 
431
static char* get_default_action( const char* dir, const char* type, gpointer user_data )
 
432
{
 
433
    GKeyFile* file;
 
434
    gboolean opened;
 
435
    char* action = NULL;
 
436
    char* path = g_build_filename( dir, "applications/defaults.list", NULL );
 
437
    file = g_key_file_new();
 
438
    opened = g_key_file_load_from_file( file, path, 0, NULL );
 
439
    g_free( path );
 
440
    if( G_LIKELY( opened ) )
 
441
        action = g_key_file_get_string( file, "Default Applications", type, NULL );
 
442
    g_key_file_free( file );
 
443
 
 
444
    /* check for existence */
 
445
    if( action )
 
446
    {
 
447
        path = mime_type_locate_desktop_file( NULL, action );
 
448
        if( G_LIKELY( path ) )
 
449
            g_free( path );
 
450
        else
 
451
        {
 
452
            g_free( action );
 
453
            action = NULL;
 
454
        }
 
455
    }
 
456
    return action;
 
457
}
 
458
 
 
459
/*
 
460
 *  Get default applications used to open this mime-type
 
461
 * The returned string was newly allocated, and should be
 
462
 * freed when no longer used.
 
463
 * If NULL is returned, that means default app is not set for this mime-type.
 
464
 */
 
465
char* mime_type_get_default_action( const char* type )
 
466
{
 
467
    /* FIXME: need to check parent types if default action of current type is not set. */
 
468
    return data_dir_foreach( (DataDirFunc)get_default_action, type, NULL );
 
469
}
 
470
 
 
471
/*
 
472
 *  Set default applications used to open this mime-type
 
473
 *  desktop_id is the name of *.desktop file.
 
474
 */
 
475
void mime_type_set_default_action( const char* type, const char* desktop_id )
 
476
{
 
477
    GKeyFile* file;
 
478
    gsize len = 0;
 
479
    char* data = NULL;
 
480
    char* dir = g_build_filename( g_get_user_data_dir(), "applications", NULL );
 
481
    char* path = g_build_filename( dir, "defaults.list", NULL );
 
482
 
 
483
    g_mkdir_with_parents( dir, 0700 );
 
484
    g_free( dir );
 
485
 
 
486
    file = g_key_file_new();
 
487
    /* Load old file content, if available */
 
488
    g_key_file_load_from_file( file, path, 0, NULL );
 
489
    g_key_file_set_string( file, "Default Applications", type, desktop_id );
 
490
    data = g_key_file_to_data( file, &len, NULL );
 
491
    g_key_file_free( file );
 
492
 
 
493
    save_to_file( path, data, len );
 
494
 
 
495
    g_free( path );
 
496
    g_free( data );
 
497
}