source: trunk/gtk/util.c @ 7693

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

(trunk gtk) #1690: Torrent is completed without ever starting (because there's nothing to download)

  • Property svn:keywords set to Date Rev Author Id
File size: 18.1 KB
Line 
1/******************************************************************************
2 * $Id: util.c 7693 2009-01-13 01:44:59Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <ctype.h> /* isxdigit() */
26#include <errno.h>
27#include <stdarg.h>
28#include <stdlib.h> /* free() */
29#include <string.h> /* strcmp() */
30
31#include <gtk/gtk.h>
32#include <glib/gi18n.h>
33#include <glib/gstdio.h> /* g_unlink() */
34#ifdef HAVE_GIO
35 #include <gio/gio.h> /* g_file_trash() */
36#endif
37#ifdef HAVE_DBUS_GLIB
38 #include <dbus/dbus-glib.h>
39#endif
40
41#include <libevent/evhttp.h>
42
43#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
44#include <libtransmission/utils.h> /* tr_inf */
45
46#include "conf.h"
47#include "hig.h"
48#include "tr-prefs.h"
49#include "util.h"
50
51char*
52tr_strlratio( char * buf,
53              double ratio,
54              size_t buflen )
55{
56    if( (int)ratio == TR_RATIO_NA )
57        g_strlcpy( buf, _( "None" ), buflen );
58    else if( (int)ratio == TR_RATIO_INF )
59        g_strlcpy( buf, "\xE2\x88\x9E", buflen );
60    else if( ratio < 10.0 )
61        g_snprintf( buf, buflen, "%'.2f", ratio );
62    else if( ratio < 100.0 )
63        g_snprintf( buf, buflen, "%'.1f", ratio );
64    else
65        g_snprintf( buf, buflen, "%'.0f", ratio );
66    return buf;
67}
68
69#define KILOBYTE_FACTOR 1024.0
70#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
71#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
72
73char*
74tr_strlsize( char *  buf,
75             guint64 size,
76             size_t  buflen )
77{
78    if( !size )
79        g_strlcpy( buf, _( "None" ), buflen );
80#if GLIB_CHECK_VERSION( 2, 16, 0 )
81    else
82    {
83        char * tmp = g_format_size_for_display( size );
84        g_strlcpy( buf, tmp, buflen );
85        g_free( tmp );
86    }
87#else
88    else if( size < (guint64)KILOBYTE_FACTOR )
89        g_snprintf( buf, buflen,
90                    ngettext( "%'u byte", "%'u bytes",
91                              (guint)size ), (guint)size );
92    else
93    {
94        gdouble displayed_size;
95        if( size < (guint64)MEGABYTE_FACTOR )
96        {
97            displayed_size = (gdouble) size / KILOBYTE_FACTOR;
98            g_snprintf( buf, buflen, _( "%'.1f KB" ), displayed_size );
99        }
100        else if( size < (guint64)GIGABYTE_FACTOR )
101        {
102            displayed_size = (gdouble) size / MEGABYTE_FACTOR;
103            g_snprintf( buf, buflen, _( "%'.1f MB" ), displayed_size );
104        }
105        else
106        {
107            displayed_size = (gdouble) size / GIGABYTE_FACTOR;
108            g_snprintf( buf, buflen, _( "%'.1f GB" ), displayed_size );
109        }
110    }
111#endif
112    return buf;
113}
114
115char*
116tr_strlspeed( char * buf,
117              double kb_sec,
118              size_t buflen )
119{
120    const double speed = kb_sec;
121
122    if( speed < 1000.0 )  /* 0.0 KB to 999.9 KB */
123        g_snprintf( buf, buflen, _( "%'.1f KB/s" ), speed );
124    else if( speed < 102400.0 ) /* 0.98 MB to 99.99 MB */
125        g_snprintf( buf, buflen, _( "%'.2f MB/s" ), ( speed / 1024 ) );
126    else if( speed < 1024000.0 ) /* 100.0 MB to 999.9 MB */
127        g_snprintf( buf, buflen, _( "%'.1f MB/s" ), ( speed / 1024 ) );
128    else /* insane speeds */
129        g_snprintf( buf, buflen, _( "%'.2f GB/s" ), ( speed / 1048576 ) );
130
131    return buf;
132}
133
134char*
135tr_strltime( char * buf,
136             int    seconds,
137             size_t buflen )
138{
139    int  days, hours, minutes;
140    char d[128], h[128], m[128], s[128];
141
142    if( seconds < 0 )
143        seconds = 0;
144
145    days = seconds / 86400;
146    hours = ( seconds % 86400 ) / 3600;
147    minutes = ( seconds % 3600 ) / 60;
148    seconds = ( seconds % 3600 ) % 60;
149
150    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days",
151                                          days ), days );
152    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours",
153                                          hours ), hours );
154    g_snprintf( m, sizeof( m ),
155                ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
156    g_snprintf( s, sizeof( s ),
157                ngettext( "%'d second", "%'d seconds", seconds ), seconds );
158
159    if( days )
160    {
161        if( days >= 4 || !hours )
162        {
163            g_strlcpy( buf, d, buflen );
164        }
165        else
166        {
167            g_snprintf( buf, buflen, "%s, %s", d, h );
168        }
169    }
170    else if( hours )
171    {
172        if( hours >= 4 || !minutes )
173        {
174            g_strlcpy( buf, h, buflen );
175        }
176        else
177        {
178            g_snprintf( buf, buflen, "%s, %s", h, m );
179        }
180    }
181    else if( minutes )
182    {
183        if( minutes >= 4 || !seconds )
184        {
185            g_strlcpy( buf, m, buflen );
186        }
187        else
188        {
189            g_snprintf( buf, buflen, "%s, %s", m, s );
190        }
191    }
192    else
193    {
194        g_strlcpy( buf, s, buflen );
195    }
196
197    return buf;
198}
199
200char *
201gtr_localtime( time_t time )
202{
203    const struct tm tm = *localtime( &time );
204    char            buf[256], *eoln;
205
206    g_strlcpy( buf, asctime( &tm ), sizeof( buf ) );
207    if( ( eoln = strchr( buf, '\n' ) ) )
208        *eoln = '\0';
209
210    return g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
211}
212
213int
214mkdir_p( const char * path,
215         mode_t       mode )
216{
217#if GLIB_CHECK_VERSION( 2, 8, 0 )
218    return !g_mkdir_with_parents( path, mode );
219#else
220    return !tr_mkdirp( path, mode );
221#endif
222}
223
224GSList *
225dupstrlist( GSList * l )
226{
227    GSList * ret = NULL;
228
229    for( ; l != NULL; l = l->next )
230        ret = g_slist_prepend( ret, g_strdup( l->data ) );
231    return g_slist_reverse( ret );
232}
233
234char *
235joinstrlist( GSList *list,
236             char *  sep )
237{
238    GSList * l;
239    GString *gstr = g_string_new ( NULL );
240
241    for( l = list; l != NULL; l = l->next )
242    {
243        g_string_append ( gstr, (char*)l->data );
244        if( l->next != NULL )
245            g_string_append ( gstr, ( sep ) );
246    }
247    return g_string_free ( gstr, FALSE );
248}
249
250void
251freestrlist( GSList *list )
252{
253    g_slist_foreach ( list, (GFunc)g_free, NULL );
254    g_slist_free ( list );
255}
256
257char *
258decode_uri( const char * uri )
259{
260    gboolean in_query = FALSE;
261    char *   ret = g_new( char, strlen( uri ) + 1 );
262    char *   out = ret;
263
264    for( ; uri && *uri; )
265    {
266        char ch = *uri;
267        if( ch == '?' )
268            in_query = TRUE;
269        else if( ch == '+' && in_query )
270            ch = ' ';
271        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
272               && isxdigit( (unsigned char)uri[2] ) )
273        {
274            char buf[3] = { uri[1], uri[2], '\0' };
275            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
276            uri += 2;
277        }
278
279        ++uri;
280        *out++ = ch;
281    }
282
283    *out = '\0';
284    return ret;
285}
286
287GSList *
288checkfilenames( int    argc,
289                char **argv )
290{
291    int      i;
292    GSList * ret = NULL;
293    char *   pwd = g_get_current_dir( );
294
295    for( i = 0; i < argc; ++i )
296    {
297        char * filename = g_path_is_absolute( argv[i] )
298                          ? g_strdup ( argv[i] )
299                          : g_build_filename( pwd, argv[i], NULL );
300
301        if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
302            ret = g_slist_prepend( ret, filename );
303        else
304            g_free( filename );
305    }
306
307    g_free( pwd );
308    return g_slist_reverse( ret );
309}
310
311static void
312onErrorResponse( GtkWidget * dialog,
313                 int resp    UNUSED,
314                 gpointer    glist )
315{
316    GSList * list = glist;
317
318    if( list )
319    {
320        callbackfunc_t func = list->data;
321        gpointer       user_data = list->next->data;
322        func( user_data );
323        g_slist_free( list );
324    }
325
326    gtk_widget_destroy( dialog );
327}
328
329static GtkWidget *
330verrmsg_full( GtkWindow *    wind,
331              callbackfunc_t func,
332              void *         data,
333              const char *   format,
334              va_list        ap )
335{
336    GtkWidget *dialog;
337    char *     msg;
338    GSList *   funcdata = NULL;
339
340    msg = g_strdup_vprintf( format, ap );
341
342    if( NULL == wind )
343        dialog = gtk_message_dialog_new(
344            NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg );
345    else
346        dialog = gtk_message_dialog_new(
347            wind,
348            GTK_DIALOG_MODAL |
349            GTK_DIALOG_DESTROY_WITH_PARENT,
350            GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
351            "%s", msg );
352
353    if( func )
354    {
355        funcdata = g_slist_append( funcdata, (gpointer)func );
356        funcdata = g_slist_append( funcdata, data );
357    }
358    g_signal_connect( dialog, "response", G_CALLBACK(
359                          onErrorResponse ), funcdata );
360    g_free( msg );
361
362    return dialog;
363}
364
365void
366addTorrentErrorDialog( GtkWidget *  child,
367                       int          err,
368                       const char * filename )
369{
370    GtkWidget *  w;
371    GtkWidget *  win;
372    const char * fmt;
373    char *       secondary;
374
375    switch( err )
376    {
377        case TR_EINVALID:
378            fmt = _( "The torrent file \"%s\" contains invalid data." );
379            break;
380
381        case TR_EDUPLICATE:
382            fmt = _( "The torrent file \"%s\" is already in use." ); break;
383
384        default:
385            fmt = _(
386                "The torrent file \"%s\" encountered an unknown error." );
387            break;
388    }
389    secondary = g_strdup_printf( fmt, filename );
390    win = ( !child || GTK_IS_WINDOW( child ) )
391          ? child
392          : gtk_widget_get_ancestor( child ? GTK_WIDGET(
393                                         child ) : NULL, GTK_TYPE_WINDOW );
394    w = gtk_message_dialog_new( GTK_WINDOW( win ),
395                               GTK_DIALOG_DESTROY_WITH_PARENT,
396                               GTK_MESSAGE_ERROR,
397                               GTK_BUTTONS_CLOSE,
398                               _( "Error opening torrent" ) );
399    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
400                                              "%s", secondary );
401    g_signal_connect_swapped( w, "response",
402                              G_CALLBACK( gtk_widget_destroy ), w );
403    gtk_widget_show_all( w );
404    g_free( secondary );
405}
406
407void
408errmsg( GtkWindow *  wind,
409        const char * format,
410        ... )
411{
412    GtkWidget * dialog;
413    va_list     ap;
414
415    va_start( ap, format );
416    dialog = verrmsg_full( wind, NULL, NULL, format, ap );
417    va_end( ap );
418
419    if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) )
420    {
421        g_signal_connect_swapped( wind, "map",
422                                  G_CALLBACK( gtk_widget_show ), dialog );
423    }
424    else
425    {
426        gtk_widget_show( dialog );
427    }
428}
429
430GtkWidget *
431errmsg_full( GtkWindow *    wind,
432             callbackfunc_t func,
433             void *         data,
434             const char *   format,
435             ... )
436{
437    GtkWidget * dialog;
438    va_list     ap;
439
440    va_start( ap, format );
441    dialog = verrmsg_full( wind, func, data, format, ap );
442    va_end( ap );
443
444    return dialog;
445}
446
447typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
448
449/* pop up the context menu if a user right-clicks.
450   if the row they right-click on isn't selected, select it. */
451
452gboolean
453on_tree_view_button_pressed( GtkWidget *      view,
454                             GdkEventButton * event,
455                             gpointer         func )
456{
457    GtkTreeView * tv = GTK_TREE_VIEW( view );
458
459    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
460    {
461        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
462        GtkTreePath *      path;
463        if( gtk_tree_view_get_path_at_pos ( tv,
464                                            (gint) event->x,
465                                            (gint) event->y,
466                                            &path, NULL, NULL, NULL ) )
467        {
468            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
469            {
470                gtk_tree_selection_unselect_all ( selection );
471                gtk_tree_selection_select_path ( selection, path );
472            }
473            gtk_tree_path_free( path );
474        }
475
476        ( (PopupFunc*)func )( view, event );
477
478        return TRUE;
479    }
480
481    return FALSE;
482}
483
484/* if the user clicked in an empty area of the list,
485 * clear all the selections. */
486gboolean
487on_tree_view_button_released( GtkWidget *      view,
488                              GdkEventButton * event,
489                              gpointer         unused UNUSED )
490{
491    GtkTreeView * tv = GTK_TREE_VIEW( view );
492
493    if( !gtk_tree_view_get_path_at_pos ( tv,
494                                         (gint) event->x,
495                                         (gint) event->y,
496                                         NULL, NULL, NULL, NULL ) )
497    {
498        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
499        gtk_tree_selection_unselect_all ( selection );
500        return TRUE;
501    }
502
503    return FALSE;
504}
505
506gpointer
507tr_object_ref_sink( gpointer object )
508{
509#if GLIB_CHECK_VERSION( 2, 10, 0 )
510    g_object_ref_sink( object );
511#else
512    g_object_ref( object );
513    gtk_object_sink( GTK_OBJECT( object ) );
514#endif
515    return object;
516}
517
518int
519tr_file_trash_or_unlink( const char * filename )
520{
521    if( filename && *filename )
522    {
523        gboolean trashed = FALSE;
524#ifdef HAVE_GIO
525        GError * err = NULL;
526        GFile *  file = g_file_new_for_path( filename );
527        trashed = g_file_trash( file, NULL, &err );
528        if( err )
529            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
530        g_clear_error( &err );
531        g_object_unref( G_OBJECT( file ) );
532       
533       
534#endif
535        if( !trashed ) {
536            if( g_unlink( filename ) ) {
537                const int err = errno;
538                g_message( "Unable to unlink file \"%s\": %s", filename, g_strerror( err ) );
539            }
540        }
541    }
542
543    return 0;
544}
545
546char*
547gtr_get_help_url( void )
548{
549    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
550    int          major, minor;
551
552    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
553    return g_strdup_printf( fmt, major, minor / 10 );
554}
555
556void
557gtr_open_file( const char * path )
558{
559    if( path )
560    {
561        gboolean opened = FALSE;
562#ifdef HAVE_GIO
563        if( !opened )
564        {
565            GFile * file = g_file_new_for_path( path );
566            char *  uri = g_file_get_uri( file );
567            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
568            g_free( uri );
569            g_object_unref( G_OBJECT( file ) );
570        }
571#endif
572        if( !opened )
573        {
574            char * argv[] = { "xdg-open", (char*)path, NULL };
575            g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
576                           NULL, NULL, NULL, NULL );
577        }
578    }
579}
580
581#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
582#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
583#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
584
585gboolean
586gtr_dbus_add_torrent( const char * filename )
587{
588    static gboolean   success = FALSE;
589
590#ifdef HAVE_DBUS_GLIB
591    DBusGProxy *      proxy = NULL;
592    GError *          err = NULL;
593    DBusGConnection * conn;
594    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
595        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
596                                            VALUE_SERVICE_OBJECT_PATH,
597                                            VALUE_SERVICE_INTERFACE );
598    else if( err )
599        g_message( "err: %s", err->message );
600    if( proxy )
601        dbus_g_proxy_call( proxy, "AddFile", &err,
602                           G_TYPE_STRING, filename,
603                           G_TYPE_INVALID,
604                           G_TYPE_BOOLEAN, &success,
605                           G_TYPE_INVALID );
606    if( err )
607        g_message( "err: %s", err->message );
608
609    g_object_unref( proxy );
610    dbus_g_connection_unref( conn );
611#endif
612    return success;
613}
614
615gboolean
616gtr_dbus_present_window( void )
617{
618    static gboolean   success = FALSE;
619
620#ifdef HAVE_DBUS_GLIB
621    DBusGProxy *      proxy = NULL;
622    GError *          err = NULL;
623    DBusGConnection * conn;
624    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
625        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
626                                            VALUE_SERVICE_OBJECT_PATH,
627                                            VALUE_SERVICE_INTERFACE );
628    else if( err )
629        g_message( "err: %s", err->message );
630    if( proxy )
631        dbus_g_proxy_call( proxy, "PresentWindow", &err,
632                           G_TYPE_INVALID,
633                           G_TYPE_BOOLEAN, &success,
634                           G_TYPE_INVALID );
635    if( err )
636        g_message( "err: %s", err->message );
637
638    g_object_unref( proxy );
639    dbus_g_connection_unref( conn );
640#endif
641    return success;
642}
643
644GtkWidget *
645gtr_button_new_from_stock( const char * stock,
646                           const char * mnemonic )
647{
648    GtkWidget * image = gtk_image_new_from_stock( stock,
649                                                  GTK_ICON_SIZE_BUTTON );
650    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
651
652    gtk_button_set_image( GTK_BUTTON( button ), image );
653    return button;
654}
655
656/***
657****
658***/
659
660guint
661gtr_timeout_add_seconds( guint        interval,
662                         GSourceFunc  function,
663                         gpointer     data )
664{
665#if GLIB_CHECK_VERSION( 2,14,0 )
666    return g_timeout_add_seconds( interval, function, data );
667#else
668    return g_timeout_add( interval*1000, function, data );
669#endif
670}
Note: See TracBrowser for help on using the repository browser.