source: trunk/gtk/util.c @ 9635

Last change on this file since 9635 was 9635, checked in by charles, 12 years ago

(trunk gtk) #2096 "magnet links" -- if a user (or, more likely, web browser) starts up a second copy of Transmission to add a magnet link via its command line, delegate that magnet link back to the already-running copy of Transmission via DBus.

  • Property svn:keywords set to Date Rev Author Id
File size: 19.3 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: util.c 9635 2009-11-29 20:35:48Z charles $
11 */
12
13#include <ctype.h> /* isxdigit() */
14#include <errno.h>
15#include <stdarg.h>
16#include <stdlib.h> /* free() */
17#include <string.h> /* strcmp() */
18
19#include <sys/types.h> /* for gtr_lockfile()'s open() */
20#include <sys/stat.h> /* for gtr_lockfile()'s open() */
21#include <fcntl.h> /* for gtr_lockfile()'s open() */
22
23#include <gtk/gtk.h>
24#include <glib/gi18n.h>
25#include <glib/gstdio.h> /* g_unlink() */
26#ifdef HAVE_GIO
27 #include <gio/gio.h> /* g_file_trash() */
28#endif
29#ifdef HAVE_DBUS_GLIB
30 #include <dbus/dbus-glib.h>
31#endif
32
33#include <evhttp.h>
34
35#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
36#include <libtransmission/utils.h> /* tr_inf */
37#include <libtransmission/version.h> /* tr_inf */
38
39#include "conf.h"
40#include "hig.h"
41#include "tr-prefs.h"
42#include "util.h"
43
44gtr_lockfile_state_t
45gtr_lockfile( const char * filename )
46{
47    gtr_lockfile_state_t ret;
48
49#ifdef WIN32
50
51    HANDLE file = CreateFile( filename,
52                              GENERIC_READ | GENERIC_WRITE,
53                              FILE_SHARE_READ | FILE_SHARE_WRITE,
54                              NULL,
55                              OPEN_ALWAYS,
56                              FILE_ATTRIBUTE_NORMAL,
57                              NULL );
58    if( file == INVALID_HANDLE_VALUE )
59        ret = GTR_LOCKFILE_EOPEN;
60    else if( !LockFile( file, 0, 0, 1, 1 ) )
61        ret = GTR_LOCKFILE_ELOCK;
62    else
63        ret = GTR_LOCKFILE_SUCCESS;
64
65#else
66
67    int fd = open( filename, O_RDWR | O_CREAT, 0666 );
68    if( fd < 0 )
69        ret = GTR_LOCKFILE_EOPEN;
70    else {
71        struct flock lk;
72        memset( &lk, 0,  sizeof( lk ) );
73        lk.l_start = 0;
74        lk.l_len = 0;
75        lk.l_type = F_WRLCK;
76        lk.l_whence = SEEK_SET;
77        if( -1 == fcntl( fd, F_SETLK, &lk ) )
78            ret = GTR_LOCKFILE_ELOCK;
79        else
80            ret = GTR_LOCKFILE_SUCCESS;
81    }
82
83#endif
84
85    return ret;
86}
87
88/***
89****
90***/
91
92
93char*
94tr_strlratio( char * buf, double ratio, size_t buflen )
95{
96    return tr_strratio( buf, buflen, ratio, "\xE2\x88\x9E" );
97}
98
99#define KILOBYTE_FACTOR 1024.0
100#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
101#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
102
103char*
104tr_strlsize( char *  buf,
105             guint64 size,
106             size_t  buflen )
107{
108    if( !size )
109        g_strlcpy( buf, _( "None" ), buflen );
110#if GLIB_CHECK_VERSION( 2, 16, 0 )
111    else
112    {
113        char * tmp = g_format_size_for_display( size );
114        g_strlcpy( buf, tmp, buflen );
115        g_free( tmp );
116    }
117#else
118    else if( size < (guint64)KILOBYTE_FACTOR )
119        g_snprintf( buf, buflen,
120                    ngettext( "%'u byte", "%'u bytes",
121                              (guint)size ), (guint)size );
122    else
123    {
124        gdouble displayed_size;
125        if( size < (guint64)MEGABYTE_FACTOR )
126        {
127            displayed_size = (gdouble) size / KILOBYTE_FACTOR;
128            g_snprintf( buf, buflen, _( "%'.1f KB" ), displayed_size );
129        }
130        else if( size < (guint64)GIGABYTE_FACTOR )
131        {
132            displayed_size = (gdouble) size / MEGABYTE_FACTOR;
133            g_snprintf( buf, buflen, _( "%'.1f MB" ), displayed_size );
134        }
135        else
136        {
137            displayed_size = (gdouble) size / GIGABYTE_FACTOR;
138            g_snprintf( buf, buflen, _( "%'.1f GB" ), displayed_size );
139        }
140    }
141#endif
142    return buf;
143}
144
145char*
146tr_strlspeed( char * buf,
147              double kb_sec,
148              size_t buflen )
149{
150    const double speed = kb_sec;
151
152    if( speed < 1000.0 )  /* 0.0 KB to 999.9 KB */
153        g_snprintf( buf, buflen, _( "%'.1f KB/s" ), speed );
154    else if( speed < 102400.0 ) /* 0.98 MB to 99.99 MB */
155        g_snprintf( buf, buflen, _( "%'.2f MB/s" ), ( speed / KILOBYTE_FACTOR ) );
156    else if( speed < 1024000.0 ) /* 100.0 MB to 999.9 MB */
157        g_snprintf( buf, buflen, _( "%'.1f MB/s" ), ( speed / MEGABYTE_FACTOR ) );
158    else /* insane speeds */
159        g_snprintf( buf, buflen, _( "%'.2f GB/s" ), ( speed / GIGABYTE_FACTOR ) );
160
161    return buf;
162}
163
164char*
165tr_strltime( char * buf,
166             int    seconds,
167             size_t buflen )
168{
169    int  days, hours, minutes;
170    char d[128], h[128], m[128], s[128];
171
172    if( seconds < 0 )
173        seconds = 0;
174
175    days = seconds / 86400;
176    hours = ( seconds % 86400 ) / 3600;
177    minutes = ( seconds % 3600 ) / 60;
178    seconds = ( seconds % 3600 ) % 60;
179
180    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days",
181                                          days ), days );
182    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours",
183                                          hours ), hours );
184    g_snprintf( m, sizeof( m ),
185                ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
186    g_snprintf( s, sizeof( s ),
187                ngettext( "%'d second", "%'d seconds", seconds ), seconds );
188
189    if( days )
190    {
191        if( days >= 4 || !hours )
192        {
193            g_strlcpy( buf, d, buflen );
194        }
195        else
196        {
197            g_snprintf( buf, buflen, "%s, %s", d, h );
198        }
199    }
200    else if( hours )
201    {
202        if( hours >= 4 || !minutes )
203        {
204            g_strlcpy( buf, h, buflen );
205        }
206        else
207        {
208            g_snprintf( buf, buflen, "%s, %s", h, m );
209        }
210    }
211    else if( minutes )
212    {
213        if( minutes >= 4 || !seconds )
214        {
215            g_strlcpy( buf, m, buflen );
216        }
217        else
218        {
219            g_snprintf( buf, buflen, "%s, %s", m, s );
220        }
221    }
222    else
223    {
224        g_strlcpy( buf, s, buflen );
225    }
226
227    return buf;
228}
229
230char *
231gtr_localtime( time_t time )
232{
233    const struct tm tm = *localtime( &time );
234    char            buf[256], *eoln;
235
236    g_strlcpy( buf, asctime( &tm ), sizeof( buf ) );
237    if( ( eoln = strchr( buf, '\n' ) ) )
238        *eoln = '\0';
239
240    return g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
241}
242
243char *
244gtr_localtime2( char * buf, time_t time, size_t buflen )
245{
246    char * tmp = gtr_localtime( time );
247    g_strlcpy( buf, tmp, buflen );
248    g_free( tmp );
249    return buf;
250}
251
252int
253gtr_mkdir_with_parents( const char * path, int mode )
254{
255#if GLIB_CHECK_VERSION( 2, 8, 0 )
256    return !g_mkdir_with_parents( path, mode );
257#else
258    return !tr_mkdirp( path, mode );
259#endif
260}
261
262GSList *
263dupstrlist( GSList * l )
264{
265    GSList * ret = NULL;
266
267    for( ; l != NULL; l = l->next )
268        ret = g_slist_prepend( ret, g_strdup( l->data ) );
269    return g_slist_reverse( ret );
270}
271
272char *
273joinstrlist( GSList *list,
274             char *  sep )
275{
276    GSList * l;
277    GString *gstr = g_string_new ( NULL );
278
279    for( l = list; l != NULL; l = l->next )
280    {
281        g_string_append ( gstr, (char*)l->data );
282        if( l->next != NULL )
283            g_string_append ( gstr, ( sep ) );
284    }
285    return g_string_free ( gstr, FALSE );
286}
287
288void
289freestrlist( GSList *list )
290{
291    g_slist_foreach ( list, (GFunc)g_free, NULL );
292    g_slist_free ( list );
293}
294
295char *
296decode_uri( const char * uri )
297{
298    gboolean in_query = FALSE;
299    char *   ret = g_new( char, strlen( uri ) + 1 );
300    char *   out = ret;
301
302    for( ; uri && *uri; )
303    {
304        char ch = *uri;
305        if( ch == '?' )
306            in_query = TRUE;
307        else if( ch == '+' && in_query )
308            ch = ' ';
309        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
310               && isxdigit( (unsigned char)uri[2] ) )
311        {
312            char buf[3] = { uri[1], uri[2], '\0' };
313            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
314            uri += 2;
315        }
316
317        ++uri;
318        *out++ = ch;
319    }
320
321    *out = '\0';
322    return ret;
323}
324
325
326gboolean
327gtr_is_supported_url( const char * str )
328{
329    return !strncmp( str, "ftp://", 6 )
330        || !strncmp( str, "http://", 7 )
331        || !strncmp( str, "https://", 8 );
332}
333
334gboolean
335gtr_is_magnet_link( const char * str )
336{
337    return !strncmp( str, "magnet:?", 8 );
338}
339
340gboolean
341gtr_is_hex_hashcode( const char * str )
342{
343    int i;
344
345    if( !str || ( strlen( str ) != 40 ) )
346        return FALSE;
347
348    for( i=0; i<40; ++i )
349        if( !isxdigit( str[i] ) )
350            return FALSE;
351
352    return TRUE;
353}
354
355GSList *
356checkfilenames( int argc, char **argv )
357{
358    int i;
359    GSList * ret = NULL;
360    char * pwd = g_get_current_dir( );
361
362    for( i=0; i<argc; ++i )
363    {
364        if( gtr_is_supported_url( argv[i] ) || gtr_is_magnet_link( argv[i] ) )
365        {
366            ret = g_slist_prepend( ret, g_strdup( argv[i] ) );
367        }
368        else /* local file */
369        {
370            char * filename = g_path_is_absolute( argv[i] )
371                            ? g_strdup ( argv[i] )
372                            : g_build_filename( pwd, argv[i], NULL );
373
374            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
375                ret = g_slist_prepend( ret, filename );
376            else {
377                if( gtr_is_hex_hashcode( argv[i] ) )
378                    ret = g_slist_prepend( ret, g_strdup_printf( "magnet:?xt=urn:btih:%s", argv[i] ) );
379                g_free( filename );
380            }
381        }
382    }
383
384    g_free( pwd );
385    return g_slist_reverse( ret );
386}
387
388void
389addTorrentErrorDialog( GtkWidget *  child,
390                       int          err,
391                       const char * filename )
392{
393    GtkWidget *  w;
394    GtkWidget *  win;
395    const char * fmt;
396    char *       secondary;
397
398    switch( err )
399    {
400        case TR_PARSE_ERR: fmt = _( "The torrent file \"%s\" contains invalid data." ); break;
401        case TR_PARSE_DUPLICATE: fmt = _( "The torrent file \"%s\" is already in use." ); break;
402        default: fmt = _( "The torrent file \"%s\" encountered an unknown error." ); break;
403    }
404    secondary = g_strdup_printf( fmt, filename );
405    win = ( !child || GTK_IS_WINDOW( child ) )
406          ? child
407          : gtk_widget_get_ancestor( child ? GTK_WIDGET(
408                                         child ) : NULL, GTK_TYPE_WINDOW );
409    w = gtk_message_dialog_new( GTK_WINDOW( win ),
410                               GTK_DIALOG_DESTROY_WITH_PARENT,
411                               GTK_MESSAGE_ERROR,
412                               GTK_BUTTONS_CLOSE,
413                               "%s", _( "Error opening torrent" ) );
414    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
415                                              "%s", secondary );
416    g_signal_connect_swapped( w, "response",
417                              G_CALLBACK( gtk_widget_destroy ), w );
418    gtk_widget_show_all( w );
419    g_free( secondary );
420}
421typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
422
423/* pop up the context menu if a user right-clicks.
424   if the row they right-click on isn't selected, select it. */
425
426gboolean
427on_tree_view_button_pressed( GtkWidget *      view,
428                             GdkEventButton * event,
429                             gpointer         func )
430{
431    GtkTreeView * tv = GTK_TREE_VIEW( view );
432
433    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
434    {
435        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
436        GtkTreePath *      path;
437        if( gtk_tree_view_get_path_at_pos ( tv,
438                                            (gint) event->x,
439                                            (gint) event->y,
440                                            &path, NULL, NULL, NULL ) )
441        {
442            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
443            {
444                gtk_tree_selection_unselect_all ( selection );
445                gtk_tree_selection_select_path ( selection, path );
446            }
447            gtk_tree_path_free( path );
448        }
449
450        if( func != NULL )
451            ( (PopupFunc*)func )( view, event );
452
453        return TRUE;
454    }
455
456    return FALSE;
457}
458
459/* if the user clicked in an empty area of the list,
460 * clear all the selections. */
461gboolean
462on_tree_view_button_released( GtkWidget *      view,
463                              GdkEventButton * event,
464                              gpointer         unused UNUSED )
465{
466    GtkTreeView * tv = GTK_TREE_VIEW( view );
467
468    if( !gtk_tree_view_get_path_at_pos ( tv,
469                                         (gint) event->x,
470                                         (gint) event->y,
471                                         NULL, NULL, NULL, NULL ) )
472    {
473        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
474        gtk_tree_selection_unselect_all ( selection );
475    }
476
477    return FALSE;
478}
479
480gpointer
481tr_object_ref_sink( gpointer object )
482{
483#if GLIB_CHECK_VERSION( 2, 10, 0 )
484    g_object_ref_sink( object );
485#else
486    g_object_ref( object );
487    gtk_object_sink( GTK_OBJECT( object ) );
488#endif
489    return object;
490}
491
492int
493tr_file_trash_or_remove( const char * filename )
494{
495    if( filename && *filename )
496    {
497        gboolean trashed = FALSE;
498#ifdef HAVE_GIO
499        GError * err = NULL;
500        GFile *  file = g_file_new_for_path( filename );
501        trashed = g_file_trash( file, NULL, &err );
502        if( err )
503            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
504        g_clear_error( &err );
505        g_object_unref( G_OBJECT( file ) );
506#endif
507
508        if( !trashed && g_remove( filename ) )
509        {
510            const int err = errno;
511            g_message( "Unable to remove file \"%s\": %s", filename, g_strerror( err ) );
512        }
513    }
514
515    return 0;
516}
517
518char*
519gtr_get_help_url( void )
520{
521    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
522    int          major, minor;
523
524    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
525    return g_strdup_printf( fmt, major, minor / 10 );
526}
527
528void
529gtr_open_file( const char * path )
530{
531    if( path )
532    {
533        gboolean opened = FALSE;
534#ifdef HAVE_GIO
535        if( !opened )
536        {
537            GFile * file = g_file_new_for_path( path );
538            char *  uri = g_file_get_uri( file );
539            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
540            g_free( uri );
541            g_object_unref( G_OBJECT( file ) );
542        }
543#endif
544        if( !opened )
545        {
546            char * argv[] = { (char*)"xdg-open", (char*)path, NULL };
547            opened = g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
548                                    NULL, NULL, NULL, NULL );
549        }
550
551        if( !opened )
552        {
553            g_message( "Unable to open \"%s\"", path );
554        }
555    }
556}
557
558#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
559#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
560#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
561
562gboolean
563gtr_dbus_add_torrent( const char * filename )
564{
565    /* FIXME: why is this static? */
566    static gboolean success = FALSE;
567
568#ifdef HAVE_DBUS_GLIB
569    char * payload;
570    gsize file_length;
571    char * file_contents = NULL;
572
573    /* If it's a file, load its contents and send them over the wire...
574     * it might be a temporary file that's going to disappear. */
575    if( g_file_get_contents( filename, &file_contents, &file_length, NULL ) )
576        payload = tr_base64_encode( file_contents, file_length, NULL );
577    else if( gtr_is_supported_url( filename ) || gtr_is_magnet_link( filename ) )
578        payload = tr_strdup( filename );
579    else
580        payload = NULL;
581
582    if( payload != NULL )
583    {
584        GError * err = NULL;
585        DBusGConnection * conn;
586        DBusGProxy * proxy = NULL;
587
588        if(( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err )))
589            proxy = dbus_g_proxy_new_for_name (conn, VALUE_SERVICE_NAME,
590                                                     VALUE_SERVICE_OBJECT_PATH,
591                                                     VALUE_SERVICE_INTERFACE );
592        else if( err )
593           g_message( "err: %s", err->message );
594
595        if( proxy )
596            dbus_g_proxy_call( proxy, "AddMetainfo", &err,
597                               G_TYPE_STRING, payload,
598                               G_TYPE_INVALID,
599                               G_TYPE_BOOLEAN, &success,
600                               G_TYPE_INVALID );
601        if( err )
602           g_message( "err: %s", err->message );
603
604        if( proxy )
605            g_object_unref( proxy );
606        if( conn )
607            dbus_g_connection_unref( conn );
608
609        tr_free( payload );
610    }
611
612    g_free( file_contents );
613
614#endif
615    return success;
616}
617
618gboolean
619gtr_dbus_present_window( void )
620{
621    static gboolean   success = FALSE;
622
623#ifdef HAVE_DBUS_GLIB
624    DBusGProxy *      proxy = NULL;
625    GError *          err = NULL;
626    DBusGConnection * conn;
627    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
628        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
629                                            VALUE_SERVICE_OBJECT_PATH,
630                                            VALUE_SERVICE_INTERFACE );
631    else if( err )
632        g_message( "err: %s", err->message );
633    if( proxy )
634        dbus_g_proxy_call( proxy, "PresentWindow", &err,
635                           G_TYPE_INVALID,
636                           G_TYPE_BOOLEAN, &success,
637                           G_TYPE_INVALID );
638    if( err )
639        g_message( "err: %s", err->message );
640
641    g_object_unref( proxy );
642    dbus_g_connection_unref( conn );
643#endif
644    return success;
645}
646
647GtkWidget *
648gtr_button_new_from_stock( const char * stock,
649                           const char * mnemonic )
650{
651    GtkWidget * image = gtk_image_new_from_stock( stock,
652                                                  GTK_ICON_SIZE_BUTTON );
653    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
654
655    gtk_button_set_image( GTK_BUTTON( button ), image );
656    return button;
657}
658
659/***
660****
661***/
662
663void
664gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip )
665{
666#if GTK_CHECK_VERSION( 2,12,0 )
667    gtk_widget_set_tooltip_text( w, tip );
668#else
669    static GtkTooltips * tips = NULL;
670    if( tips == NULL )
671        tips = gtk_tooltips_new( );
672    gtk_tooltips_set_tip( tips, w, tip, NULL );
673#endif
674}
675
676void
677gtr_toolbar_set_orientation( GtkToolbar      * toolbar,
678                             GtkOrientation    orientation )
679{
680#if GTK_CHECK_VERSION( 2,16,0 )
681    gtk_orientable_set_orientation( GTK_ORIENTABLE( toolbar ), orientation );
682#else
683    gtk_toolbar_set_orientation( toolbar, orientation );
684#endif
685}
686
687/***
688****
689***/
690
691#if !GTK_CHECK_VERSION( 2,12,0 )
692struct gtr_func_data
693{
694    GSourceFunc function;
695    gpointer data;
696};
697
698static gboolean
699gtr_thread_func( gpointer data )
700{
701    struct gtr_func_data * idle_data = data;
702    gboolean more;
703
704    gdk_threads_enter( );
705    more = idle_data->function( idle_data->data );
706    gdk_threads_leave( );
707
708    if( !more )
709        g_free( data );
710
711    return more;
712}
713#endif
714
715void
716gtr_idle_add( GSourceFunc function, gpointer data )
717{
718#if GTK_CHECK_VERSION( 2,12,0 )
719    gdk_threads_add_idle( function, data );
720#else
721    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
722    d->function = function;
723    d->data = data;
724    g_idle_add( gtr_thread_func, d );
725#endif
726}
727
728guint
729gtr_timeout_add_seconds( guint seconds, GSourceFunc function, gpointer data )
730{
731#if GTK_CHECK_VERSION( 2,14,0 )
732    return gdk_threads_add_timeout_seconds( seconds, function, data );
733#elif GTK_CHECK_VERSION( 2,12,0 )
734    return gdk_threads_add_timeout( seconds*1000, function, data );
735#else
736    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
737    d->function = function;
738    d->data = data;
739    return g_timeout_add( seconds*1000, gtr_thread_func, d );
740#endif
741}
Note: See TracBrowser for help on using the repository browser.