source: trunk/gtk/util.c @ 9628

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

(trunk gtk) fix r9625 oops on adding torrents by their raw 40 character hex hashcode from the commandline in the GTK+ client

  • Property svn:keywords set to Date Rev Author Id
File size: 19.0 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 9628 2009-11-29 08:29:57Z 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( 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#ifdef HAVE_DBUS_GLIB
568    DBusGProxy * proxy = NULL;
569    GError * err = NULL;
570    DBusGConnection * conn;
571    char * file_contents;
572    gsize file_length;
573
574    if( g_file_get_contents( filename, &file_contents, &file_length, NULL ) )
575    {
576        char * b64 = tr_base64_encode( file_contents, file_length, NULL );
577        if(( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err )))
578            proxy = dbus_g_proxy_new_for_name (conn, VALUE_SERVICE_NAME,
579                                                     VALUE_SERVICE_OBJECT_PATH,
580                                                     VALUE_SERVICE_INTERFACE );
581        else if( err )
582           g_message( "err: %s", err->message );
583        if( proxy )
584            dbus_g_proxy_call( proxy, "AddMetainfo", &err,
585                               G_TYPE_STRING, b64,
586                               G_TYPE_INVALID,
587                               G_TYPE_BOOLEAN, &success,
588                               G_TYPE_INVALID );
589        if( err )
590           g_message( "err: %s", err->message );
591
592        if( proxy )
593            g_object_unref( proxy );
594        if( conn )
595            dbus_g_connection_unref( conn );
596
597        tr_free( b64 );
598        g_free( file_contents );
599    }
600    else g_message( "couldn't read %s", filename );
601
602#endif
603    return success;
604}
605
606gboolean
607gtr_dbus_present_window( void )
608{
609    static gboolean   success = FALSE;
610
611#ifdef HAVE_DBUS_GLIB
612    DBusGProxy *      proxy = NULL;
613    GError *          err = NULL;
614    DBusGConnection * conn;
615    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
616        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
617                                            VALUE_SERVICE_OBJECT_PATH,
618                                            VALUE_SERVICE_INTERFACE );
619    else if( err )
620        g_message( "err: %s", err->message );
621    if( proxy )
622        dbus_g_proxy_call( proxy, "PresentWindow", &err,
623                           G_TYPE_INVALID,
624                           G_TYPE_BOOLEAN, &success,
625                           G_TYPE_INVALID );
626    if( err )
627        g_message( "err: %s", err->message );
628
629    g_object_unref( proxy );
630    dbus_g_connection_unref( conn );
631#endif
632    return success;
633}
634
635GtkWidget *
636gtr_button_new_from_stock( const char * stock,
637                           const char * mnemonic )
638{
639    GtkWidget * image = gtk_image_new_from_stock( stock,
640                                                  GTK_ICON_SIZE_BUTTON );
641    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
642
643    gtk_button_set_image( GTK_BUTTON( button ), image );
644    return button;
645}
646
647/***
648****
649***/
650
651void
652gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip )
653{
654#if GTK_CHECK_VERSION( 2,12,0 )
655    gtk_widget_set_tooltip_text( w, tip );
656#else
657    static GtkTooltips * tips = NULL;
658    if( tips == NULL )
659        tips = gtk_tooltips_new( );
660    gtk_tooltips_set_tip( tips, w, tip, NULL );
661#endif
662}
663
664void
665gtr_toolbar_set_orientation( GtkToolbar      * toolbar,
666                             GtkOrientation    orientation )
667{
668#if GTK_CHECK_VERSION( 2,16,0 )
669    gtk_orientable_set_orientation( GTK_ORIENTABLE( toolbar ), orientation );
670#else
671    gtk_toolbar_set_orientation( toolbar, orientation );
672#endif
673}
674
675/***
676****
677***/
678
679#if !GTK_CHECK_VERSION( 2,12,0 )
680struct gtr_func_data
681{
682    GSourceFunc function;
683    gpointer data;
684};
685
686static gboolean
687gtr_thread_func( gpointer data )
688{
689    struct gtr_func_data * idle_data = data;
690    gboolean more;
691
692    gdk_threads_enter( );
693    more = idle_data->function( idle_data->data );
694    gdk_threads_leave( );
695
696    if( !more )
697        g_free( data );
698
699    return more;
700}
701#endif
702
703void
704gtr_idle_add( GSourceFunc function, gpointer data )
705{
706#if GTK_CHECK_VERSION( 2,12,0 )
707    gdk_threads_add_idle( function, data );
708#else
709    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
710    d->function = function;
711    d->data = data;
712    g_idle_add( gtr_thread_func, d );
713#endif
714}
715
716guint
717gtr_timeout_add_seconds( guint seconds, GSourceFunc function, gpointer data )
718{
719#if GTK_CHECK_VERSION( 2,14,0 )
720    return gdk_threads_add_timeout_seconds( seconds, function, data );
721#elif GTK_CHECK_VERSION( 2,12,0 )
722    return gdk_threads_add_timeout( seconds*1000, function, data );
723#else
724    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
725    d->function = function;
726    d->data = data;
727    return g_timeout_add( seconds*1000, gtr_thread_func, d );
728#endif
729}
Note: See TracBrowser for help on using the repository browser.