source: trunk/gtk/main.c @ 1615

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

Add preference option to disable PEX.

  • Property svn:keywords set to Date Rev Author Id
File size: 32.0 KB
Line 
1/******************************************************************************
2 * $Id: main.c 1615 2007-03-31 19:19:27Z 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_FILES,
93    ACT_DEBUG,
94    ACT_SEPARATOR2,
95    ACT_PREF,
96    ACT_SEPARATOR3,
97    ACT_CLOSE,
98    ACT_QUIT,
99    ACT_ICON,
100    ACTION_COUNT,
101};
102
103struct
104{
105    const char * label;
106    const char * icon;
107    guint        key;
108    int          flags;
109    const char * tooltip;
110}
111actions[] =
112{
113    { NULL,        GTK_STOCK_ADD,       'o', ACTF_WHEREVER | ACTF_ALWAYS,
114      N_("Add a new torrent") },
115    { N_("Start"), GTK_STOCK_EXECUTE,     0, ACTF_WHEREVER | ACTF_INACTIVE,
116      N_("Start a torrent that is not running") },
117    { NULL,        GTK_STOCK_STOP,        0, ACTF_WHEREVER | ACTF_ACTIVE,
118      N_("Stop a torrent that is running") },
119    { NULL,        GTK_STOCK_REMOVE,      0, ACTF_WHEREVER | ACTF_WHATEVER,
120      N_("Remove a torrent") },
121    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
122    { NULL,        GTK_STOCK_PROPERTIES,  0, ACTF_WHEREVER | ACTF_WHATEVER,
123      N_("Show additional information about a torrent") },
124    { N_("Files"), NULL,                  0, ACTF_MENU     | ACTF_WHATEVER,
125      N_("Show a list of the files in a torrent"), },
126    { N_("Open debug window"), NULL,      0, ACTF_MENU     | ACTF_ALWAYS,
127      NULL },
128    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
129    { NULL,        GTK_STOCK_PREFERENCES, 0, ACTF_WHEREVER | ACTF_ALWAYS,
130      N_("Customize application behavior") },
131    { NULL,        NULL,                  0, ACTF_SEPARATOR, NULL },
132    { NULL,        GTK_STOCK_CLOSE,       0, ACTF_MENU     | ACTF_ALWAYS,
133      N_("Close the main window") },
134    { NULL,        GTK_STOCK_QUIT,        0, ACTF_MENU     | ACTF_ALWAYS,
135      N_("Exit the program") },
136    /* this isn't a terminator for the list, it's ACT_ICON */
137    { NULL,        NULL,                  0, 0, NULL },
138};
139
140#define CBDATA_PTR              "callback-data-pointer"
141
142static sig_atomic_t global_sigcount = 0;
143
144static GList *
145readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
146static gboolean
147sendremote( GList * files, gboolean sendquit );
148static void
149gtksetup( int * argc, char *** argv );
150static void
151appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused );
152static void
153winsetup( struct cbdata * cbdata, TrWindow * wind );
154static void
155remakewind( struct cbdata * cbdata );
156static void
157makeicon( struct cbdata * cbdata );
158static gboolean
159winclose( GtkWidget * widget, GdkEvent * event, gpointer gdata );
160static void
161wannaquit( void * vdata );
162static gboolean
163exitcheck(gpointer gdata);
164static void
165setupdrag(GtkWidget *widget, struct cbdata *data);
166static void
167gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
168        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
169
170static void
171readinitialprefs( struct cbdata * cbdata );
172static void
173prefschanged( GtkWidget * widget, int id, gpointer data );
174static void
175setpex( tr_torrent_t * tor, void * arg );
176static gboolean
177updatemodel(gpointer gdata);
178static void
179boolwindclosed(GtkWidget *widget, gpointer gdata);
180static void
181windact(GtkWidget *widget, int action, gpointer gdata);
182static GList *
183getselection( struct cbdata * cbdata );
184static void
185handleaction( struct cbdata *data, int action );
186
187static void
188addtorrents(void *vdata, void *state, GList *files,
189            const char *dir, guint flags);
190static void
191savetorrents(struct cbdata *data);
192static void
193safepipe(void);
194static void
195setupsighandlers(void);
196static void
197fatalsig(int sig);
198
199int
200main( int argc, char ** argv )
201{
202    GtkWindow  * mainwind;
203    char       * err;
204    benc_val_t * state;
205    GList      * argfiles;
206    gboolean     didinit, didlock, sendquit, startpaused;
207
208    safepipe();                 /* ignore SIGPIPE */
209    argfiles = readargs( argc, argv, &sendquit, &startpaused );
210    didinit = cf_init( tr_getPrefsDirectory(), NULL );
211    didlock = FALSE;
212    if( didinit )
213    {
214        /* maybe send remote commands, also try cf_lock() */
215        didlock = sendremote( argfiles, sendquit );
216    }
217    setupsighandlers();         /* set up handlers for fatal signals */
218    gtksetup( &argc, &argv );   /* set up gtk and gettext */
219
220    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
221        ( didlock || cf_lock( &err ) ) )
222    {
223        /* create main window now to be a parent to any error dialogs */
224        mainwind = GTK_WINDOW( tr_window_new() );
225
226        /* try to load prefs and saved state */
227        cf_loadprefs( &err );
228        if( NULL != err )
229        {
230            errmsg( mainwind, "%s", err );
231            g_free( err );
232        }
233        state = cf_loadstate( &err );
234        if( NULL != err )
235        {
236            errmsg( mainwind, "%s", err );
237            g_free( err );
238        }
239
240        msgwin_loadpref();      /* set message level here before tr_init() */
241        appsetup( TR_WINDOW( mainwind ), state, argfiles, startpaused );
242        cf_freestate( state );
243    }
244    else
245    {
246        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
247                                      NULL, "%s", err ) );
248        g_free( err );
249    }
250
251    freestrlist(argfiles);
252
253    gtk_main();
254
255    return 0;
256}
257
258GList *
259readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
260{
261    struct option opts[] =
262    {
263        { "help",    no_argument, NULL, 'h' },
264        { "paused",  no_argument, NULL, 'p' },
265        { "quit",    no_argument, NULL, 'q' },
266        { "version", no_argument, NULL, 'v' },
267        { NULL, 0, NULL, 0 }
268    };
269    int          opt;
270    const char * name;
271
272    *sendquit    = FALSE;
273    *startpaused = FALSE;
274
275    gtk_parse_args( &argc, &argv );
276    name = g_get_prgname();
277
278    while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
279    {
280        switch( opt )
281        {
282            case 'p':
283                *startpaused = TRUE;
284                break;
285            case 'q':
286                *sendquit = TRUE;
287                break;
288            case 'v':
289            case 'h':
290                printf(
291_("usage: %s [-hpq] [files...]\n"
292  "\n"
293  "Transmission %s (r%d) http://transmission.m0k.org/\n"
294  "A free, lightweight BitTorrent client with a simple, intuitive interface\n"
295  "\n"
296  "  -h --help    display this message and exit\n"
297  "  -p --paused  start with all torrents paused\n"
298  "  -q --quit    request that the running %s instance quit\n"
299  "\n"
300  "Only one instance of %s may run at one time. Multiple\n"
301  "torrent files may be loaded at startup by adding them to the command\n"
302  "line. If %s is already running, those torrents will be\n"
303  "opened in the running instance.\n"),
304                        name, VERSION_STRING, VERSION_REVISION,
305                        name, name, name );
306                exit(0);
307                break;
308        }
309    }
310
311    argc -= optind;
312    argv += optind;
313
314    return checkfilenames( argc, argv );
315}
316
317static gboolean
318sendremote( GList * files, gboolean sendquit )
319{
320    gboolean didlock;
321
322    didlock = cf_lock( NULL );
323
324    if( NULL != files )
325    {
326        /* send files if there's another instance, otherwise start normally */
327        if( !didlock )
328        {
329            exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
330        }
331    }
332
333    if( sendquit )
334    {
335        /* either send a quit message or exit if no other instance */
336        if( !didlock )
337        {
338            exit( ipc_sendquit_blocking() ? 0 : 1 );
339        }
340        exit( 0 );
341    }
342
343    return didlock;
344}
345
346static void
347gtksetup( int * argc, char *** argv )
348{
349    GdkPixbuf * icon;
350
351    gtk_init( argc, argv );
352
353    bindtextdomain( "transmission-gtk", LOCALEDIR );
354    bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
355    textdomain( "transmission-gtk" );
356
357    g_set_application_name( _("Transmission") );
358
359    /* tweak some style properties in dialogs to get closer to the GNOME HiG */
360    gtk_rc_parse_string(
361        "style \"transmission-standard\"\n"
362        "{\n"
363        "    GtkDialog::action-area-border  = 6\n"
364        "    GtkDialog::button-spacing      = 12\n"
365        "    GtkDialog::content-area-border = 6\n"
366        "}\n"
367        "widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
368
369    icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
370    gtk_window_set_default_icon( icon );
371    g_object_unref( icon );
372}
373
374static void
375appsetup( TrWindow * wind, benc_val_t * state, GList * args, gboolean paused )
376{
377    GType types[] =
378    {
379        /* info->name, info->totalSize, status,     error,      errorString, */
380        G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
381        /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
382        G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
383        /* peersUploading, peersDownloading, downloaded,    uploaded */
384        G_TYPE_INT,        G_TYPE_INT,       G_TYPE_UINT64, G_TYPE_UINT64,
385        /* the TrTorrent object */
386        TR_TORRENT_TYPE,
387    };
388    struct cbdata * cbdata;
389    GtkListStore  * store;
390    guint           flags;
391
392    /* create the model used to store torrent data */
393    g_assert( ALEN( types ) == MC_ROW_COUNT );
394    store = gtk_list_store_newv( MC_ROW_COUNT, types );
395
396    /* fill out cbdata */
397    cbdata = g_new0( struct cbdata, 1 );
398    cbdata->back       = tr_backend_new();
399    cbdata->wind       = NULL;
400    cbdata->model      = GTK_TREE_MODEL(store);
401    cbdata->icon       = NULL;
402    cbdata->prefs      = NULL;
403    cbdata->timer      = 0;
404    cbdata->msgwinopen = FALSE;
405    cbdata->closing    = FALSE;
406
407    /* apply a few prefs */
408    readinitialprefs( cbdata );
409
410    /* set up main window */
411    winsetup( cbdata, wind );
412
413    /* add torrents from command-line and saved state */
414    flags = addactionflag( tr_prefs_get( PREF_ID_ADDIPC ) );
415    g_assert( !( flags & ( TR_TORNEW_PAUSED | TR_TORNEW_RUNNING ) ) );
416    if( paused )
417    {
418        flags |= TR_TORNEW_PAUSED;
419    }
420    addtorrents( cbdata, state, args, NULL, flags );
421
422    /* start model update timer */
423    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
424    updatemodel( cbdata );
425
426    /* this shows the window */
427    tr_window_size_hack( wind );
428
429    /* set up the ipc socket now that we're ready to get torrents from it */
430    ipc_socket_setup( GTK_WINDOW( wind ), addtorrents, wannaquit, cbdata );
431}
432
433static void
434winsetup( struct cbdata * cbdata, TrWindow * wind )
435{
436    int ii;
437    GtkWidget  * drag;
438
439    g_assert( ACTION_COUNT == ALEN( actions ) );
440    g_assert( NULL == cbdata->wind );
441    cbdata->wind = GTK_WINDOW( wind );
442    for( ii = 0; ii < ALEN( actions ); ii++ )
443    {
444        tr_window_action_add( wind, ii, actions[ii].flags,
445                              gettext( actions[ii].label ), actions[ii].icon,
446                              gettext( actions[ii].tooltip ),
447                              actions[ii].key );
448    }
449    g_object_set( wind, "model", cbdata->model,
450                        "double-click-action", ACT_INFO, NULL);
451
452    g_signal_connect( wind, "action",       G_CALLBACK( windact  ), cbdata );
453    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
454   
455    g_object_get( wind, "drag-widget", &drag, NULL );
456    setupdrag( drag, cbdata );
457}
458
459static void
460remakewind( struct cbdata * cbdata )
461{
462    GtkWidget * win;
463
464    if( NULL != cbdata->wind )
465    {
466        return;
467    }
468
469    /* create window */
470    win = tr_window_new();
471    winsetup( cbdata, TR_WINDOW( win ) );
472
473    /* this shows the window */
474    tr_window_size_hack( TR_WINDOW( win ) );
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  tr_info_t *in;
832  GtkTreeIter iter;
833  float up, down;
834
835  if( !data->closing && 0 < global_sigcount )
836  {
837      wannaquit( data );
838      return FALSE;
839  }
840
841  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
842    do {
843      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
844      st = tr_torrent_stat(tor);
845      in = tr_torrent_info(tor);
846      g_object_unref(tor);
847      /* XXX find out if setting the same data emits changed signal */
848      gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
849        MC_SIZE, in->totalSize, MC_STAT, st->status, MC_ERR, st->error,
850        MC_TERR, st->errorString, MC_PROG, st->progress,
851        MC_DRATE, st->rateDownload, MC_URATE, st->rateUpload, MC_ETA, st->eta,
852        MC_PEERS, st->peersTotal, MC_UPEERS, st->peersUploading,
853        MC_DPEERS, st->peersDownloading, MC_DOWN, st->downloaded,
854        MC_UP, st->uploaded, -1);
855    } while(gtk_tree_model_iter_next(data->model, &iter));
856  }
857
858  /* update the main window's statusbar and toolbar buttons */
859  if( NULL != data->wind )
860  {
861      tr_torrentRates( tr_backend_handle( data->back ), &down, &up );
862      tr_window_update( TR_WINDOW( data->wind ), down, up );
863  }
864
865  /* check for politely stopped torrents unless we're exiting */
866  if( !data->closing )
867  {
868      tr_backend_torrents_stopped( data->back, FALSE );
869  }
870
871  /* update the message window */
872  msgwin_update();
873
874  return TRUE;
875}
876
877static void
878boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
879  gboolean *preachy_gcc = gdata;
880 
881  *preachy_gcc = FALSE;
882}
883
884static void
885windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
886{
887    g_assert( 0 <= action );
888    handleaction( gdata, action );
889}
890
891/* returns a GList containing a GtkTreeRowReference to each selected row */
892static GList *
893getselection( struct cbdata * cbdata )
894{
895    GtkTreeSelection    * sel;
896    GList               * rows, * ii;
897    GtkTreeRowReference * ref;
898
899    if( NULL == cbdata->wind )
900    {
901        return NULL;
902    }
903    g_object_get( cbdata->wind, "selection", &sel, NULL );
904    rows = gtk_tree_selection_get_selected_rows( sel, NULL );
905    for( ii = rows; NULL != ii; ii = ii->next )
906    {
907        ref = gtk_tree_row_reference_new( cbdata->model, ii->data );
908        gtk_tree_path_free( ii->data );
909        ii->data = ref;
910    }
911
912    return rows;
913}
914
915static void
916handleaction( struct cbdata * data, int act )
917{
918  GList *rows, *ii;
919  GtkTreePath *path;
920  GtkTreeIter iter;
921  TrTorrent *tor;
922  int status;
923  gboolean changed;
924  GtkWidget * win;
925
926  g_assert( ACTION_COUNT > act );
927
928  switch( act )
929  {
930      case ACT_OPEN:
931          makeaddwind( data->wind, addtorrents, data );
932          return;
933      case ACT_PREF:
934          if( NULL != data->prefs )
935          {
936              return;
937          }
938          data->prefs = tr_prefs_new_with_parent( data->wind );
939          g_signal_connect( data->prefs, "prefs-changed",
940                            G_CALLBACK( prefschanged ), data );
941          g_signal_connect( data->prefs, "destroy",
942                            G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
943          gtk_widget_show( GTK_WIDGET( data->prefs ) );
944          return;
945      case ACT_DEBUG:
946          if( !data->msgwinopen )
947          {
948              data->msgwinopen = TRUE;
949              win = msgwin_create();
950              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
951                                &data->msgwinopen );
952          }
953          return;
954      case ACT_ICON:
955          remakewind( data );
956          return;
957      case ACT_CLOSE:
958          if( NULL != data->wind )
959          {
960              winclose( NULL, NULL, data );
961          }
962          return;
963      case ACT_QUIT:
964          askquit( data->wind, wannaquit, data );
965          return;
966      case ACT_SEPARATOR1:
967      case ACT_SEPARATOR2:
968      case ACT_SEPARATOR3:
969          return;
970      case ACT_START:
971      case ACT_STOP:
972      case ACT_DELETE:
973      case ACT_INFO:
974      case ACT_FILES:
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, tor );
1015                  break;
1016              case ACT_FILES:
1017                  makefileswind( data->wind, tor );
1018                  break;
1019              case ACT_OPEN:
1020              case ACT_PREF:
1021              case ACT_DEBUG:
1022              case ACT_ICON:
1023              case ACT_CLOSE:
1024              case ACT_QUIT:
1025              case ACT_SEPARATOR1:
1026              case ACT_SEPARATOR2:
1027              case ACT_SEPARATOR3:
1028              case ACTION_COUNT:
1029                  break;
1030          }
1031      }
1032      g_object_unref(tor);
1033    }
1034    if(NULL != path)
1035      gtk_tree_path_free(path);
1036    gtk_tree_row_reference_free(ii->data);
1037  }
1038  g_list_free(rows);
1039
1040  if(changed) {
1041    savetorrents(data);
1042    updatemodel(data);
1043  }
1044}
1045
1046static void
1047addtorrents(void *vdata, void *state, GList *files,
1048            const char *dir, guint flags) {
1049  struct cbdata *data = vdata;
1050  GList *torlist, *errlist, *ii;
1051  char *errstr;
1052  TrTorrent *tor;
1053  GtkTreeIter iter;
1054  const char * pref;
1055
1056  errlist = NULL;
1057  torlist = NULL;
1058
1059  if( NULL != state )
1060  {
1061      torlist = tr_backend_load_state( data->back, state, flags, &errlist );
1062  }
1063
1064  if(NULL != files) {
1065    if( NULL == dir )
1066    {
1067        pref = tr_prefs_get( PREF_ID_ASKDIR );
1068        if( NULL != pref && strbool( pref ) )
1069        {
1070            promptfordir( data->wind, addtorrents, data, files, flags );
1071            files = NULL;
1072        }
1073        dir = getdownloaddir();
1074    }
1075    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
1076      errstr = NULL;
1077      tor = tr_torrent_new(G_OBJECT(data->back), ii->data, dir,
1078                           flags, &errstr);
1079      if(NULL != tor)
1080        torlist = g_list_append(torlist, tor);
1081      if(NULL != errstr)
1082        errlist = g_list_append(errlist, errstr);
1083    }
1084  }
1085
1086  for(ii = g_list_first(torlist); NULL != ii; ii = ii->next) {
1087    gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
1088    gtk_list_store_set(GTK_LIST_STORE(data->model), &iter,
1089                       MC_TORRENT, ii->data, -1);
1090    /* we will always ref a torrent before politely stopping it */
1091    g_signal_connect(ii->data, "politely_stopped",
1092                     G_CALLBACK(g_object_unref), data);
1093    g_object_unref(ii->data);
1094  }
1095
1096  if(NULL != errlist) {
1097    errstr = joinstrlist(errlist, "\n");
1098    errmsg( data->wind, ngettext( "Failed to load torrent file:\n%s",
1099                                  "Failed to load torrent files:\n%s",
1100                                  g_list_length( errlist ) ), errstr );
1101    g_list_foreach(errlist, (GFunc)g_free, NULL);
1102    g_list_free(errlist);
1103    g_free(errstr);
1104  }
1105
1106  if(NULL != torlist) {
1107    updatemodel(data);
1108    savetorrents(data);
1109  }
1110}
1111
1112static void
1113savetorrents( struct cbdata *data )
1114{
1115    char * errstr;
1116
1117    tr_backend_save_state( data->back, &errstr );
1118    if( NULL != errstr )
1119    {
1120        errmsg( data->wind, "%s", errstr );
1121        g_free( errstr );
1122    }
1123}
1124
1125static void
1126safepipe(void) {
1127  struct sigaction sa;
1128
1129  bzero(&sa, sizeof(sa));
1130  sa.sa_handler = SIG_IGN;
1131  sigaction(SIGPIPE, &sa, NULL);
1132}
1133
1134static void
1135setupsighandlers(void) {
1136  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1137  struct sigaction sa;
1138  int ii;
1139
1140  bzero(&sa, sizeof(sa));
1141  sa.sa_handler = fatalsig;
1142  for(ii = 0; ii < ALEN(sigs); ii++)
1143    sigaction(sigs[ii], &sa, NULL);
1144}
1145
1146static void
1147fatalsig(int sig) {
1148  struct sigaction sa;
1149
1150  if(SIGCOUNT_MAX <= ++global_sigcount) {
1151    bzero(&sa, sizeof(sa));
1152    sa.sa_handler = SIG_DFL;
1153    sigaction(sig, &sa, NULL);
1154    raise(sig);
1155  }
1156}
Note: See TracBrowser for help on using the repository browser.