source: branches/0.7x/gtk/main.c @ 1784

Last change on this file since 1784 was 1784, checked in by joshe, 15 years ago

Merge r1781 from trunk.

  • Property svn:keywords set to Date Rev Author Id
File size: 32.3 KB
Line 
1/******************************************************************************
2 * $Id: main.c 1784 2007-04-23 19:55:46Z joshe $
3 *
4 * Copyright (c) 2005-2007 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 <sys/param.h>
26#include <errno.h>
27#include <getopt.h>
28#include <signal.h>
29#include <string.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <gtk/gtk.h>
36#include <glib/gi18n.h>
37#include <glib/gstdio.h>
38
39#include "conf.h"
40#include "dialogs.h"
41#include "ipc.h"
42#include "msgwin.h"
43#include "tr_backend.h"
44#include "tr_cell_renderer_progress.h"
45#include "tr_icon.h"
46#include "tr_prefs.h"
47#include "tr_torrent.h"
48#include "tr_window.h"
49#include "util.h"
50
51#include "transmission.h"
52
53#include "img_icon_full.h"
54
55/* time in seconds to wait for torrents to stop when exiting */
56#define TRACKER_EXIT_TIMEOUT    10
57
58/* interval in milliseconds to update the torrent list display */
59#define UPDATE_INTERVAL         1000
60
61/* interval in milliseconds to check for stopped torrents and update display */
62#define EXIT_CHECK_INTERVAL     500
63
64/* number of fatal signals required to cause an immediate exit */
65#define SIGCOUNT_MAX            3
66
67struct cbdata {
68    TrBackend    * back;
69    GtkWindow    * wind;
70    GtkTreeModel * model;
71    TrIcon       * icon;
72    TrPrefs      * prefs;
73    guint          timer;
74    gboolean       msgwinopen;
75    gboolean       closing;
76};
77
78struct exitdata {
79    struct cbdata * cbdata;
80    time_t          started;
81    guint           timer;
82};
83
84enum
85{
86    ACT_OPEN = 0,
87    ACT_START,
88    ACT_STOP,
89    ACT_DELETE,
90    ACT_SEPARATOR1,
91    ACT_INFO,
92    ACT_DEBUG,
93    ACT_SEPARATOR2,
94    ACT_PREF,
95    ACT_SEPARATOR3,
96    ACT_CLOSE,
97    ACT_QUIT,
98    ACT_ICON,
99    ACTION_COUNT,
100};
101
102struct
103{
104    const char * label;
105    const char * icon;
106    guint        key;
107    int          flags;
108    const char * tooltip;
109}
110actions[] =
111{
112    { NULL,        GTK_STOCK_ADD,       'o', ACTF_WHEREVER | ACTF_ALWAYS,
113      N_("Add a new torrent") },
114    { N_("Start"), GTK_STOCK_EXECUTE,     0, ACTF_WHEREVER | ACTF_INACTIVE,
115      N_("Start a torrent that is not running") },
116    { NULL,        GTK_STOCK_STOP,        0, ACTF_WHEREVER | ACTF_ACTIVE,
117      N_("Stop a torrent that is running") },
118    { NULL,        GTK_STOCK_REMOVE,      0, ACTF_WHEREVER | ACTF_WHATEVER,
119      N_("Remove a torrent") },
120    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
121    { NULL,        GTK_STOCK_PROPERTIES,  0, ACTF_WHEREVER | ACTF_WHATEVER,
122      N_("Show additional information about a torrent") },
123    { N_("Open debug window"), NULL,      0, ACTF_MENU     | ACTF_ALWAYS,
124      NULL },
125    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
126    { NULL,        GTK_STOCK_PREFERENCES, 0, ACTF_WHEREVER | ACTF_ALWAYS,
127      N_("Customize application behavior") },
128    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
129    { NULL,        GTK_STOCK_CLOSE,       0, ACTF_MENU     | ACTF_ALWAYS,
130      N_("Close the main window") },
131    { NULL,        GTK_STOCK_QUIT,        0, ACTF_MENU     | ACTF_ALWAYS,
132      N_("Exit the program") },
133    /* this isn't a terminator for the list, it's ACT_ICON */
134    { NULL,        NULL,                  0, 0, NULL },
135};
136
137#define CBDATA_PTR              "callback-data-pointer"
138
139static sig_atomic_t global_sigcount = 0;
140
141static GList *
142readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
143static gboolean
144sendremote( GList * files, gboolean sendquit );
145static void
146gtksetup( int * argc, char *** argv );
147static void
148appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused );
149static void
150winsetup( struct cbdata * cbdata, TrWindow * wind );
151static void
152iconclick( struct cbdata * cbdata );
153static void
154makeicon( struct cbdata * cbdata );
155static gboolean
156winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata );
157static void
158wannaquit( void * vdata );
159static gboolean
160exitcheck(gpointer gdata);
161static void
162setupdrag(GtkWidget *widget, struct cbdata *data);
163static void
164gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
165        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
166
167static void
168readinitialprefs( struct cbdata * cbdata );
169static void
170prefschanged( GtkWidget * widget, int id, gpointer data );
171static void
172setpex( tr_torrent_t * tor, void * arg );
173static gboolean
174updatemodel(gpointer gdata);
175static void
176boolwindclosed(GtkWidget *widget, gpointer gdata);
177static void
178windact(GtkWidget *widget, int action, gpointer gdata);
179static GList *
180getselection( struct cbdata * cbdata );
181static void
182handleaction( struct cbdata *data, int action );
183
184static void
185addtorrents(void *vdata, void *state, GList *files,
186            const char *dir, guint flags);
187static void
188savetorrents(struct cbdata *data);
189static void
190safepipe(void);
191static void
192setupsighandlers(void);
193static void
194fatalsig(int sig);
195
196int
197main( int argc, char ** argv )
198{
199    GtkWindow  * mainwind;
200    char       * err;
201    benc_val_t * state;
202    GList      * argfiles;
203    gboolean     didinit, didlock, sendquit, startpaused;
204
205    safepipe();                 /* ignore SIGPIPE */
206    argfiles = readargs( argc, argv, &sendquit, &startpaused );
207    didinit = cf_init( tr_getPrefsDirectory(), NULL );
208    didlock = FALSE;
209    if( didinit )
210    {
211        /* maybe send remote commands, also try cf_lock() */
212        didlock = sendremote( argfiles, sendquit );
213    }
214    setupsighandlers();         /* set up handlers for fatal signals */
215    gtksetup( &argc, &argv );   /* set up gtk and gettext */
216
217    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
218        ( didlock || cf_lock( &err ) ) )
219    {
220        /* create main window now to be a parent to any error dialogs */
221        mainwind = GTK_WINDOW( tr_window_new() );
222
223        /* try to load prefs and saved state */
224        cf_loadprefs( &err );
225        if( NULL != err )
226        {
227            errmsg( mainwind, "%s", err );
228            g_free( err );
229        }
230        state = cf_loadstate( &err );
231        if( NULL != err )
232        {
233            errmsg( mainwind, "%s", err );
234            g_free( err );
235        }
236
237        msgwin_loadpref();      /* set message level here before tr_init() */
238        appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused );
239        cf_freestate( state );
240    }
241    else
242    {
243        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
244                                      NULL, "%s", err ) );
245        g_free( err );
246    }
247
248    freestrlist(argfiles);
249
250    gtk_main();
251
252    return 0;
253}
254
255GList *
256readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
257{
258    struct option opts[] =
259    {
260        { "help",    no_argument, NULL, 'h' },
261        { "paused",  no_argument, NULL, 'p' },
262        { "quit",    no_argument, NULL, 'q' },
263        { "version", no_argument, NULL, 'v' },
264        { NULL, 0, NULL, 0 }
265    };
266    int          opt;
267    const char * name;
268
269    *sendquit    = FALSE;
270    *startpaused = FALSE;
271
272    gtk_parse_args( &argc, &argv );
273    name = g_get_prgname();
274
275    while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
276    {
277        switch( opt )
278        {
279            case 'p':
280                *startpaused = TRUE;
281                break;
282            case 'q':
283                *sendquit = TRUE;
284                break;
285            case 'v':
286            case 'h':
287                printf(
288_("usage: %s [-hpq] [files...]\n"
289  "\n"
290  "Transmission %s (r%d) http://transmission.m0k.org/\n"
291  "A free, lightweight BitTorrent client with a simple, intuitive interface\n"
292  "\n"
293  "  -h --help    display this message and exit\n"
294  "  -p --paused  start with all torrents paused\n"
295  "  -q --quit    request that the running %s instance quit\n"
296  "\n"
297  "Only one instance of %s may run at one time. Multiple\n"
298  "torrent files may be loaded at startup by adding them to the command\n"
299  "line. If %s is already running, those torrents will be\n"
300  "opened in the running instance.\n"),
301                        name, VERSION_STRING, VERSION_REVISION,
302                        name, name, name );
303                exit(0);
304                break;
305        }
306    }
307
308    argc -= optind;
309    argv += optind;
310
311    return checkfilenames( argc, argv );
312}
313
314static gboolean
315sendremote( GList * files, gboolean sendquit )
316{
317    gboolean didlock;
318
319    didlock = cf_lock( NULL );
320
321    if( NULL != files )
322    {
323        /* send files if there's another instance, otherwise start normally */
324        if( !didlock )
325        {
326            exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
327        }
328    }
329
330    if( sendquit )
331    {
332        /* either send a quit message or exit if no other instance */
333        if( !didlock )
334        {
335            exit( ipc_sendquit_blocking() ? 0 : 1 );
336        }
337        exit( 0 );
338    }
339
340    return didlock;
341}
342
343static void
344gtksetup( int * argc, char *** argv )
345{
346    GdkPixbuf * icon;
347
348    gtk_init( argc, argv );
349
350    bindtextdomain( "transmission-gtk", LOCALEDIR );
351    bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
352    textdomain( "transmission-gtk" );
353
354    g_set_application_name( _("Transmission") );
355
356    /* tweak some style properties in dialogs to get closer to the GNOME HiG */
357    gtk_rc_parse_string(
358        "style \"transmission-standard\"\n"
359        "{\n"
360        "    GtkDialog::action-area-border  = 6\n"
361        "    GtkDialog::button-spacing      = 12\n"
362        "    GtkDialog::content-area-border = 6\n"
363        "}\n"
364        "widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
365
366    icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
367    gtk_window_set_default_icon( icon );
368    g_object_unref( icon );
369}
370
371static void
372appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
373{
374    GType types[] =
375    {
376        /* info->name, info->totalSize, status,     error,      errorString, */
377        G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
378        /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
379        G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
380        /* peersUploading, peersDownloading, seeders,    leechers */
381        G_TYPE_INT,        G_TYPE_INT,       G_TYPE_INT, G_TYPE_INT,
382        /* completedFromTracker, downloaded,    uploaded       left */
383        G_TYPE_INT,              G_TYPE_UINT64, G_TYPE_UINT64, G_TYPE_UINT64,
384        /* tracker,            the TrTorrent object */
385        TR_TRACKER_BOXED_TYPE, TR_TORRENT_TYPE,
386    };
387    struct cbdata * cbdata;
388    GtkListStore  * store;
389    guint           flags;
390
391    /* create the model used to store torrent data */
392    g_assert( ALEN( types ) == MC_ROW_COUNT );
393    store = gtk_list_store_newv( MC_ROW_COUNT, types );
394
395    /* fill out cbdata */
396    cbdata = g_new0( struct cbdata, 1 );
397    cbdata->back       = tr_backend_new();
398    cbdata->wind       = NULL;
399    cbdata->model      = GTK_TREE_MODEL(store);
400    cbdata->icon       = NULL;
401    cbdata->prefs      = NULL;
402    cbdata->timer      = 0;
403    cbdata->msgwinopen = FALSE;
404    cbdata->closing    = FALSE;
405
406    /* apply a few prefs */
407    readinitialprefs( cbdata );
408
409    /* set up main window */
410    winsetup( cbdata, wind );
411
412    /* add torrents from command-line and saved state */
413    flags = addactionflag( tr_prefs_get( PREF_ID_ADDIPC ) );
414    g_assert( !( flags & ( TR_TORNEW_PAUSED | TR_TORNEW_RUNNING ) ) );
415    if( paused )
416    {
417        flags |= TR_TORNEW_PAUSED;
418    }
419    addtorrents( cbdata, state, args, NULL, flags );
420
421    /* start model update timer */
422    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
423    updatemodel( cbdata );
424
425    /* set up the ipc socket now that we're ready to get torrents from it */
426    ipc_socket_setup( GTK_WINDOW( wind ), addtorrents, wannaquit, cbdata );
427
428    /* show the window */
429    tr_window_show( wind );
430}
431
432static void
433winsetup( struct cbdata * cbdata, TrWindow * wind )
434{
435    int ii;
436    GtkWidget  * drag;
437
438    g_assert( ACTION_COUNT == ALEN( actions ) );
439    g_assert( NULL == cbdata->wind );
440    cbdata->wind = GTK_WINDOW( wind );
441    for( ii = 0; ii < ALEN( actions ); ii++ )
442    {
443        tr_window_action_add( wind, ii, actions[ii].flags,
444                              gettext( actions[ii].label ), actions[ii].icon,
445                              gettext( actions[ii].tooltip ),
446                              actions[ii].key );
447    }
448    g_object_set( wind, "model", cbdata->model,
449                        "double-click-action", ACT_INFO, NULL);
450
451    g_signal_connect( wind, "action",       G_CALLBACK( windact  ), cbdata );
452    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
453   
454    g_object_get( wind, "drag-widget", &drag, NULL );
455    setupdrag( drag, cbdata );
456}
457
458static void
459iconclick( struct cbdata * cbdata )
460{
461    GtkWidget * win;
462
463    if( NULL != cbdata->wind )
464    {
465        /* close window  */
466        winclose( NULL, NULL, cbdata );
467    }
468    else
469    {
470        /* create window */
471        win = tr_window_new();
472        winsetup( cbdata, TR_WINDOW( win ) );
473        tr_window_show( TR_WINDOW( win ) );
474    }
475}
476
477static void
478makeicon( struct cbdata * cbdata )
479{
480    TrIcon * icon;
481    int      ii;
482
483    if( NULL != cbdata->icon )
484    {
485        return;
486    }
487
488    icon = tr_icon_new();
489    for( ii = 0; ii < ALEN( actions ); ii++ )
490    {
491        tr_icon_action_add( TR_ICON( icon ), ii, actions[ii].flags,
492                              gettext( actions[ii].label ), actions[ii].icon );
493    }
494    g_object_set( icon, "activate-action", ACT_ICON, NULL);
495    g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata );
496
497    cbdata->icon = icon;
498}
499
500static gboolean
501winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata )
502{
503    struct cbdata * cbdata;
504
505    cbdata = gdata;
506
507    if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) )
508    {
509        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
510        cbdata->wind = NULL;
511    }
512    else
513    {
514        askquit( cbdata->wind, wannaquit, cbdata );
515    }
516
517    /* don't propagate event further */
518    return TRUE;
519}
520
521static void
522wannaquit( void * vdata )
523{
524  struct cbdata * data;
525  struct exitdata *edata;
526  GtkTreeIter iter;
527  TrTorrent *tor;
528
529  data = vdata;
530  if( data->closing )
531  {
532      return;
533  }
534  data->closing = TRUE;
535
536  /* stop the update timer */
537  if(0 < data->timer)
538    g_source_remove(data->timer);
539  data->timer = 0;
540
541  /*
542    Add a reference to all torrents in the list, which will be removed
543    when the politely-stopped signal is emitted.  This is necessary
544    because a reference is added when a torrent is removed
545    from the model and tr_torrent_stop_polite() is called on it.
546  */
547  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
548    do
549      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
550    while(gtk_tree_model_iter_next(data->model, &iter));
551  }
552
553  /* try to politely stop all the torrents */
554  tr_backend_stop_torrents(data->back);
555
556  /* shut down nat traversal */
557  tr_natTraversalEnable(tr_backend_handle(data->back), 0);
558
559  /* set things up to wait for torrents to stop */
560  edata = g_new0(struct exitdata, 1);
561  edata->cbdata = data;
562  edata->started = time(NULL);
563  /* check if torrents are still running */
564  if(exitcheck(edata)) {
565    /* yes, start the exit timer and disable widgets */
566    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
567    if( NULL != data->wind )
568    {
569        gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
570    }
571  }
572}
573
574static gboolean
575exitcheck( gpointer gdata )
576{
577    struct exitdata    * edata;
578    struct cbdata      * cbdata;
579    tr_handle_status_t * hstat;
580
581    edata  = gdata;
582    cbdata = edata->cbdata;
583    hstat  = tr_handleStatus( tr_backend_handle( cbdata->back ) );
584
585    /* keep going if we haven't hit the exit timeout and
586       we either have torrents left or nat traversal is active */
587    if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
588    {
589        if( !tr_backend_torrents_stopped( cbdata->back, FALSE ) ||
590            TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus )
591        {
592            updatemodel( cbdata );
593            return TRUE;
594        }
595    }
596    else
597    {
598        /* time the remaining torrents out so they signal politely-stopped */
599        tr_backend_torrents_stopped( cbdata->back, TRUE );
600    }
601
602    /* exit otherwise */
603    if( 0 < edata->timer )
604    {
605        g_source_remove( edata->timer );
606    }
607    g_free( edata );
608    /* The prefs window need to be destroyed first as destroying it may
609       trigger callbacks that use cbdata->back. Ick. */
610    if( NULL != cbdata->prefs )
611    {
612        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
613    }
614    g_object_unref( G_OBJECT( cbdata->back ) );
615    if( NULL != cbdata->wind )
616    {
617        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
618    }
619    g_object_unref( cbdata->model );
620    if( NULL != cbdata->icon )
621    {
622        g_object_unref( cbdata->icon );
623    }
624    g_assert( 0 == cbdata->timer );
625    g_free( cbdata );
626    gtk_main_quit();
627
628    return FALSE;
629}
630
631static void
632gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
633        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
634        gpointer gdata) {
635  struct cbdata *data = gdata;
636  char prefix[] = "file:";
637  char *files, *decoded, *deslashed, *hostless;
638  int ii, len;
639  GList *errs;
640  struct stat sb;
641  int prelen = strlen(prefix);
642  GList *paths, *freeables;
643
644#ifdef DND_DEBUG
645  char *sele = gdk_atom_name(sel->selection);
646  char *targ = gdk_atom_name(sel->target);
647  char *type = gdk_atom_name(sel->type);
648
649  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
650          sele, targ, type, sel->format, sel->length);
651  g_free(sele);
652  g_free(targ);
653  g_free(type);
654  if(8 == sel->format) {
655    for(ii = 0; ii < sel->length; ii++)
656      fprintf(stderr, "%02X ", sel->data[ii]);
657    fprintf(stderr, "\n");
658  }
659#endif
660
661  errs = NULL;
662  paths = NULL;
663  freeables = NULL;
664  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
665     8 == sel->format) {
666    /* split file list on carriage returns and linefeeds */
667    files = g_new(char, sel->length + 1);
668    memcpy(files, sel->data, sel->length);
669    files[sel->length] = '\0';
670    for(ii = 0; '\0' != files[ii]; ii++)
671      if('\015' == files[ii] || '\012' == files[ii])
672        files[ii] = '\0';
673
674    /* try to get a usable filename out of the URI supplied and add it */
675    for(ii = 0; ii < sel->length; ii += len + 1) {
676      if('\0' == files[ii])
677        len = 0;
678      else {
679        len = strlen(files + ii);
680        /* de-urlencode the URI */
681        decoded = urldecode(files + ii, len);
682        freeables = g_list_append(freeables, decoded);
683        if(g_utf8_validate(decoded, -1, NULL)) {
684          /* remove the file: prefix */
685          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
686            deslashed = decoded + prelen;
687            /* trim excess / characters from the beginning */
688            while('/' == deslashed[0] && '/' == deslashed[1])
689              deslashed++;
690            /* if the file doesn't exist, the first part might be a hostname */
691            if(0 > g_stat(deslashed, &sb) &&
692               NULL != (hostless = strchr(deslashed + 1, '/')) &&
693               0 == g_stat(hostless, &sb))
694              deslashed = hostless;
695            /* finally, add it to the list of torrents to try adding */
696            paths = g_list_append(paths, deslashed);
697          }
698        }
699      }
700    }
701
702    /* try to add any torrents we found */
703    if( NULL != paths )
704    {
705        addtorrents( data, NULL, paths, NULL,
706                     addactionflag( tr_prefs_get( PREF_ID_ADDSTD ) ) );
707    }
708    freestrlist(freeables);
709    g_free(files);
710  }
711
712  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
713}
714
715static void
716setupdrag(GtkWidget *widget, struct cbdata *data) {
717  GtkTargetEntry targets[] = {
718    { "STRING",     0, 0 },
719    { "text/plain", 0, 0 },
720    { "text/uri-list", 0, 0 },
721  };
722
723  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
724
725  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
726                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
727}
728
729static void
730readinitialprefs( struct cbdata * cbdata )
731{
732    int prefs[] =
733    {
734        PREF_ID_PORT,
735        PREF_ID_USEDOWNLIMIT,
736        PREF_ID_USEUPLIMIT,
737        PREF_ID_NAT,
738        PREF_ID_ICON,
739        PREF_ID_PEX,
740    };
741    int ii;
742
743    for( ii = 0; ALEN( prefs ) > ii; ii++ )
744    {
745        prefschanged( NULL, prefs[ii], cbdata );
746    }
747}
748
749static void
750prefschanged( GtkWidget * widget SHUTUP, int id, gpointer data )
751{
752    struct cbdata * cbdata;
753    tr_handle_t   * tr;
754    int             num;
755    gboolean        boolval;
756
757    cbdata = data;
758    tr     = tr_backend_handle( cbdata->back );
759
760    switch( id )
761    {
762        case PREF_ID_PORT:
763            tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
764            break;
765
766        case PREF_ID_USEDOWNLIMIT:
767        case PREF_ID_DOWNLIMIT:
768            num = -1;
769            if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) )
770            {
771                num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT );
772            }
773            tr_setGlobalDownloadLimit( tr, num );
774            break;
775
776        case PREF_ID_USEUPLIMIT:
777        case PREF_ID_UPLIMIT:
778            num = -1;
779            if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) )
780            {
781                num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT );
782            }
783            tr_setGlobalUploadLimit( tr, num );
784            break;
785
786        case PREF_ID_NAT:
787            tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
788            break;
789
790        case PREF_ID_ICON:
791            if( tr_prefs_get_bool_with_default( id ) )
792            {
793                makeicon( cbdata );
794            }
795            else if( NULL != cbdata->icon )
796            {
797                g_object_unref( cbdata->icon );
798                cbdata->icon = NULL;
799            }
800            break;
801
802        case PREF_ID_PEX:
803            boolval = tr_prefs_get_bool_with_default( id );
804            tr_torrentIterate( tr, setpex, &boolval );
805            break;
806
807        case PREF_ID_DIR:
808        case PREF_ID_ASKDIR:
809        case PREF_ID_ADDSTD:
810        case PREF_ID_ADDIPC:
811        case PREF_ID_MSGLEVEL:
812        case PREF_MAX_ID:
813            break;
814    }
815}
816
817void
818setpex( tr_torrent_t * tor, void * arg )
819{
820    gboolean * val;
821
822    val = arg;
823    tr_torrentDisablePex( tor, !(*val) );
824}
825
826gboolean
827updatemodel(gpointer gdata) {
828  struct cbdata *data = gdata;
829  TrTorrent *tor;
830  tr_stat_t *st;
831  GtkTreeIter iter;
832  float up, down;
833
834  if( !data->closing && 0 < global_sigcount )
835  {
836      wannaquit( data );
837      return FALSE;
838  }
839
840  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
841    do {
842      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
843      st = tr_torrent_stat(tor);
844      g_object_unref(tor);
845      /* XXX find out if setting the same data emits changed signal */
846      gtk_list_store_set( GTK_LIST_STORE( data->model ), &iter,
847          MC_STAT,   st->status,               MC_ERR,     st->error,
848          MC_TERR,   st->errorString,          MC_PROG,    st->progress,
849          MC_DRATE,  st->rateDownload,         MC_URATE,   st->rateUpload,
850          MC_ETA,    st->eta,                  MC_PEERS,   st->peersTotal,
851          MC_UPEERS, st->peersUploading,       MC_DPEERS,  st->peersDownloading,
852          MC_SEED,   st->seeders,              MC_LEECH,   st->leechers,
853          MC_DONE,   st->completedFromTracker, MC_TRACKER, st->tracker,
854          MC_DOWN,   st->downloaded,           MC_UP,      st->uploaded,
855          MC_LEFT,   st->left, -1);
856    } while(gtk_tree_model_iter_next(data->model, &iter));
857  }
858
859  /* update the main window's statusbar and toolbar buttons */
860  if( NULL != data->wind )
861  {
862      tr_torrentRates( tr_backend_handle( data->back ), &down, &up );
863      tr_window_update( TR_WINDOW( data->wind ), down, up );
864  }
865
866  /* check for politely stopped torrents unless we're exiting */
867  if( !data->closing )
868  {
869      tr_backend_torrents_stopped( data->back, FALSE );
870  }
871
872  /* update the message window */
873  msgwin_update();
874
875  return TRUE;
876}
877
878static void
879boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
880  gboolean *preachy_gcc = gdata;
881 
882  *preachy_gcc = FALSE;
883}
884
885static void
886windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
887{
888    g_assert( 0 <= action );
889    handleaction( gdata, action );
890}
891
892/* returns a GList containing a GtkTreeRowReference to each selected row */
893static GList *
894getselection( struct cbdata * cbdata )
895{
896    GtkTreeSelection    * sel;
897    GList               * rows, * ii;
898    GtkTreeRowReference * ref;
899
900    if( NULL == cbdata->wind )
901    {
902        return NULL;
903    }
904    g_object_get( cbdata->wind, "selection", &sel, NULL );
905    rows = gtk_tree_selection_get_selected_rows( sel, NULL );
906    for( ii = rows; NULL != ii; ii = ii->next )
907    {
908        ref = gtk_tree_row_reference_new( cbdata->model, ii->data );
909        gtk_tree_path_free( ii->data );
910        ii->data = ref;
911    }
912
913    return rows;
914}
915
916static void
917handleaction( struct cbdata * data, int act )
918{
919  GList *rows, *ii;
920  GtkTreePath *path;
921  GtkTreeIter iter;
922  TrTorrent *tor;
923  int status;
924  gboolean changed;
925  GtkWidget * win;
926
927  g_assert( ACTION_COUNT > act );
928
929  switch( act )
930  {
931      case ACT_OPEN:
932          makeaddwind( data->wind, addtorrents, data );
933          return;
934      case ACT_PREF:
935          if( NULL != data->prefs )
936          {
937              return;
938          }
939          data->prefs = tr_prefs_new_with_parent( data->wind );
940          g_signal_connect( data->prefs, "prefs-changed",
941                            G_CALLBACK( prefschanged ), data );
942          g_signal_connect( data->prefs, "destroy",
943                            G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
944          gtk_widget_show( GTK_WIDGET( data->prefs ) );
945          return;
946      case ACT_DEBUG:
947          if( !data->msgwinopen )
948          {
949              data->msgwinopen = TRUE;
950              win = msgwin_create();
951              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
952                                &data->msgwinopen );
953          }
954          return;
955      case ACT_ICON:
956          iconclick( data );
957          return;
958      case ACT_CLOSE:
959          if( NULL != data->wind )
960          {
961              winclose( NULL, NULL, data );
962          }
963          return;
964      case ACT_QUIT:
965          askquit( data->wind, wannaquit, data );
966          return;
967      case ACT_SEPARATOR1:
968      case ACT_SEPARATOR2:
969      case ACT_SEPARATOR3:
970          return;
971      case ACT_START:
972      case ACT_STOP:
973      case ACT_DELETE:
974      case ACT_INFO:
975      case ACTION_COUNT:
976          break;
977  }
978
979  /* get a list of references to selected rows */
980  rows = getselection( data );
981
982  changed = FALSE;
983  for(ii = rows; NULL != ii; ii = ii->next) {
984    if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
985       gtk_tree_model_get_iter(data->model, &iter, path)) {
986      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
987                         MC_STAT, &status, -1);
988      if( ACT_ISAVAIL( actions[act].flags, status ) )
989
990      {
991          switch( act )
992          {
993              case ACT_START:
994                  tr_torrentStart( tr_torrent_handle( tor ) );
995                  changed = TRUE;
996                  break;
997              case ACT_STOP:
998                  tr_torrentStop( tr_torrent_handle( tor ) );
999                  changed = TRUE;
1000                  break;
1001              case ACT_DELETE:
1002                  /* tor will be unref'd in the politely_stopped handler */
1003                  g_object_ref( tor );
1004                  tr_torrent_stop_politely( tor );
1005                  if( TR_FLAG_SAVE & tr_torrent_info( tor )->flags )
1006                  {
1007                      tr_torrentRemoveSaved( tr_torrent_handle( tor ) );
1008                  }
1009                  gtk_list_store_remove( GTK_LIST_STORE( data->model ),
1010                                         &iter );
1011                  changed = TRUE;
1012                  break;
1013              case ACT_INFO:
1014                  makeinfowind( data->wind, data->model, path, tor );
1015                  break;
1016              case ACT_OPEN:
1017              case ACT_PREF:
1018              case ACT_DEBUG:
1019              case ACT_ICON:
1020              case ACT_CLOSE:
1021              case ACT_QUIT:
1022              case ACT_SEPARATOR1:
1023              case ACT_SEPARATOR2:
1024              case ACT_SEPARATOR3:
1025              case ACTION_COUNT:
1026                  break;
1027          }
1028      }
1029      g_object_unref(tor);
1030    }
1031    if(NULL != path)
1032      gtk_tree_path_free(path);
1033    gtk_tree_row_reference_free(ii->data);
1034  }
1035  g_list_free(rows);
1036
1037  if(changed) {
1038    savetorrents(data);
1039    updatemodel(data);
1040  }
1041}
1042
1043static void
1044addtorrents(void *vdata, void *state, GList *files,
1045            const char *dir, guint flags) {
1046  struct cbdata *data = vdata;
1047  GList *torlist, *errlist, *ii;
1048  char *errstr;
1049  TrTorrent *tor;
1050  GtkTreeIter iter;
1051  const char * pref;
1052  tr_info_t *in;
1053
1054  errlist = NULL;
1055  torlist = NULL;
1056
1057  if( NULL != state )
1058  {
1059      torlist = tr_backend_load_state( data->back, state, flags, &errlist );
1060  }
1061
1062  if(NULL != files) {
1063    if( NULL == dir )
1064    {
1065        pref = tr_prefs_get( PREF_ID_ASKDIR );
1066        if( NULL != pref && strbool( pref ) )
1067        {
1068            promptfordir( data->wind, addtorrents, data, files, flags );
1069            files = NULL;
1070        }
1071        dir = getdownloaddir();
1072    }
1073    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
1074      errstr = NULL;
1075      tor = tr_torrent_new(G_OBJECT(data->back), ii->data, dir,
1076                           flags, &errstr);
1077      if(NULL != tor)
1078        torlist = g_list_append(torlist, tor);
1079      if(NULL != errstr)
1080        errlist = g_list_append(errlist, errstr);
1081    }
1082  }
1083
1084  for( ii = g_list_first( torlist ); NULL != ii; ii = ii->next )
1085  {
1086      gtk_list_store_append( GTK_LIST_STORE( data->model ), &iter );
1087      in = tr_torrent_info( ii->data );
1088      gtk_list_store_set( GTK_LIST_STORE( data->model ), &iter,
1089                          MC_NAME,    in->name,
1090                          MC_SIZE,    in->totalSize,
1091                          MC_TORRENT, ii->data, -1);
1092      /* we will always ref a torrent before politely stopping it */
1093      g_signal_connect( ii->data, "politely_stopped",
1094                        G_CALLBACK( g_object_unref ), data );
1095      g_object_unref( ii->data );
1096  }
1097
1098  if(NULL != errlist) {
1099    errstr = joinstrlist(errlist, "\n");
1100    errmsg( data->wind, ngettext( "Failed to load torrent file:\n%s",
1101                                  "Failed to load torrent files:\n%s",
1102                                  g_list_length( errlist ) ), errstr );
1103    g_list_foreach(errlist, (GFunc)g_free, NULL);
1104    g_list_free(errlist);
1105    g_free(errstr);
1106  }
1107
1108  if(NULL != torlist) {
1109    updatemodel(data);
1110    savetorrents(data);
1111  }
1112}
1113
1114static void
1115savetorrents( struct cbdata *data )
1116{
1117    char * errstr;
1118
1119    tr_backend_save_state( data->back, &errstr );
1120    if( NULL != errstr )
1121    {
1122        errmsg( data->wind, "%s", errstr );
1123        g_free( errstr );
1124    }
1125}
1126
1127static void
1128safepipe(void) {
1129  struct sigaction sa;
1130
1131  bzero(&sa, sizeof(sa));
1132  sa.sa_handler = SIG_IGN;
1133  sigaction(SIGPIPE, &sa, NULL);
1134}
1135
1136static void
1137setupsighandlers(void) {
1138  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1139  struct sigaction sa;
1140  int ii;
1141
1142  bzero(&sa, sizeof(sa));
1143  sa.sa_handler = fatalsig;
1144  for(ii = 0; ii < ALEN(sigs); ii++)
1145    sigaction(sigs[ii], &sa, NULL);
1146}
1147
1148static void
1149fatalsig(int sig) {
1150  struct sigaction sa;
1151
1152  if(SIGCOUNT_MAX <= ++global_sigcount) {
1153    bzero(&sa, sizeof(sa));
1154    sa.sa_handler = SIG_DFL;
1155    sigaction(sig, &sa, NULL);
1156    raise(sig);
1157  }
1158}
Note: See TracBrowser for help on using the repository browser.