source: trunk/gtk/main.c @ 1656

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

Add remaining file download to properties window.

  • Property svn:keywords set to Date Rev Author Id
File size: 32.2 KB
Line 
1/******************************************************************************
2 * $Id: main.c 1656 2007-04-04 00:56:17Z 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
152remakewind( 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
459remakewind( struct cbdata * cbdata )
460{
461    GtkWidget * win;
462
463    if( NULL != cbdata->wind )
464    {
465        return;
466    }
467
468    /* create window */
469    win = tr_window_new();
470    winsetup( cbdata, TR_WINDOW( win ) );
471    tr_window_show( TR_WINDOW( win ) );
472}
473
474static void
475makeicon( struct cbdata * cbdata )
476{
477    TrIcon * icon;
478    int      ii;
479
480    if( NULL != cbdata->icon )
481    {
482        return;
483    }
484
485    icon = tr_icon_new();
486    for( ii = 0; ii < ALEN( actions ); ii++ )
487    {
488        tr_icon_action_add( TR_ICON( icon ), ii, actions[ii].flags,
489                              gettext( actions[ii].label ), actions[ii].icon );
490    }
491    g_object_set( icon, "activate-action", ACT_ICON, NULL);
492    g_signal_connect( icon, "action", G_CALLBACK( windact ), cbdata );
493
494    cbdata->icon = icon;
495}
496
497static gboolean
498winclose( GtkWidget * widget SHUTUP, GdkEvent * event SHUTUP, gpointer gdata )
499{
500    struct cbdata * cbdata;
501
502    cbdata = gdata;
503
504    if( NULL != cbdata->icon && tr_icon_docked( cbdata->icon ) )
505    {
506        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
507        cbdata->wind = NULL;
508    }
509    else
510    {
511        askquit( cbdata->wind, wannaquit, cbdata );
512    }
513
514    /* don't propagate event further */
515    return TRUE;
516}
517
518static void
519wannaquit( void * vdata )
520{
521  struct cbdata * data;
522  struct exitdata *edata;
523  GtkTreeIter iter;
524  TrTorrent *tor;
525
526  data = vdata;
527  if( data->closing )
528  {
529      return;
530  }
531  data->closing = TRUE;
532
533  /* stop the update timer */
534  if(0 < data->timer)
535    g_source_remove(data->timer);
536  data->timer = 0;
537
538  /*
539    Add a reference to all torrents in the list, which will be removed
540    when the politely-stopped signal is emitted.  This is necessary
541    because a reference is added when a torrent is removed
542    from the model and tr_torrent_stop_polite() is called on it.
543  */
544  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
545    do
546      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
547    while(gtk_tree_model_iter_next(data->model, &iter));
548  }
549
550  /* try to politely stop all the torrents */
551  tr_backend_stop_torrents(data->back);
552
553  /* shut down nat traversal */
554  tr_natTraversalEnable(tr_backend_handle(data->back), 0);
555
556  /* set things up to wait for torrents to stop */
557  edata = g_new0(struct exitdata, 1);
558  edata->cbdata = data;
559  edata->started = time(NULL);
560  /* check if torrents are still running */
561  if(exitcheck(edata)) {
562    /* yes, start the exit timer and disable widgets */
563    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
564    if( NULL != data->wind )
565    {
566        gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
567    }
568  }
569}
570
571static gboolean
572exitcheck( gpointer gdata )
573{
574    struct exitdata    * edata;
575    struct cbdata      * cbdata;
576    tr_handle_status_t * hstat;
577
578    edata  = gdata;
579    cbdata = edata->cbdata;
580    hstat  = tr_handleStatus( tr_backend_handle( cbdata->back ) );
581
582    /* keep going if we haven't hit the exit timeout and
583       we either have torrents left or nat traversal is active */
584    if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
585    {
586        if( !tr_backend_torrents_stopped( cbdata->back, FALSE ) ||
587            TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus )
588        {
589            updatemodel( cbdata );
590            return TRUE;
591        }
592    }
593    else
594    {
595        /* time the remaining torrents out so they signal politely-stopped */
596        tr_backend_torrents_stopped( cbdata->back, TRUE );
597    }
598
599    /* exit otherwise */
600    if( 0 < edata->timer )
601    {
602        g_source_remove( edata->timer );
603    }
604    g_free( edata );
605    /* The prefs window need to be destroyed first as destroying it may
606       trigger callbacks that use cbdata->back. Ick. */
607    if( NULL != cbdata->prefs )
608    {
609        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
610    }
611    g_object_unref( G_OBJECT( cbdata->back ) );
612    if( NULL != cbdata->wind )
613    {
614        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
615    }
616    g_object_unref( cbdata->model );
617    if( NULL != cbdata->icon )
618    {
619        g_object_unref( cbdata->icon );
620    }
621    g_assert( 0 == cbdata->timer );
622    g_free( cbdata );
623    gtk_main_quit();
624
625    return FALSE;
626}
627
628static void
629gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
630        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
631        gpointer gdata) {
632  struct cbdata *data = gdata;
633  char prefix[] = "file:";
634  char *files, *decoded, *deslashed, *hostless;
635  int ii, len;
636  GList *errs;
637  struct stat sb;
638  int prelen = strlen(prefix);
639  GList *paths, *freeables;
640
641#ifdef DND_DEBUG
642  char *sele = gdk_atom_name(sel->selection);
643  char *targ = gdk_atom_name(sel->target);
644  char *type = gdk_atom_name(sel->type);
645
646  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
647          sele, targ, type, sel->format, sel->length);
648  g_free(sele);
649  g_free(targ);
650  g_free(type);
651  if(8 == sel->format) {
652    for(ii = 0; ii < sel->length; ii++)
653      fprintf(stderr, "%02X ", sel->data[ii]);
654    fprintf(stderr, "\n");
655  }
656#endif
657
658  errs = NULL;
659  paths = NULL;
660  freeables = NULL;
661  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
662     8 == sel->format) {
663    /* split file list on carriage returns and linefeeds */
664    files = g_new(char, sel->length + 1);
665    memcpy(files, sel->data, sel->length);
666    files[sel->length] = '\0';
667    for(ii = 0; '\0' != files[ii]; ii++)
668      if('\015' == files[ii] || '\012' == files[ii])
669        files[ii] = '\0';
670
671    /* try to get a usable filename out of the URI supplied and add it */
672    for(ii = 0; ii < sel->length; ii += len + 1) {
673      if('\0' == files[ii])
674        len = 0;
675      else {
676        len = strlen(files + ii);
677        /* de-urlencode the URI */
678        decoded = urldecode(files + ii, len);
679        freeables = g_list_append(freeables, decoded);
680        if(g_utf8_validate(decoded, -1, NULL)) {
681          /* remove the file: prefix */
682          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
683            deslashed = decoded + prelen;
684            /* trim excess / characters from the beginning */
685            while('/' == deslashed[0] && '/' == deslashed[1])
686              deslashed++;
687            /* if the file doesn't exist, the first part might be a hostname */
688            if(0 > g_stat(deslashed, &sb) &&
689               NULL != (hostless = strchr(deslashed + 1, '/')) &&
690               0 == g_stat(hostless, &sb))
691              deslashed = hostless;
692            /* finally, add it to the list of torrents to try adding */
693            paths = g_list_append(paths, deslashed);
694          }
695        }
696      }
697    }
698
699    /* try to add any torrents we found */
700    if( NULL != paths )
701    {
702        addtorrents( data, NULL, paths, NULL,
703                     addactionflag( tr_prefs_get( PREF_ID_ADDSTD ) ) );
704    }
705    freestrlist(freeables);
706    g_free(files);
707  }
708
709  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
710}
711
712static void
713setupdrag(GtkWidget *widget, struct cbdata *data) {
714  GtkTargetEntry targets[] = {
715    { "STRING",     0, 0 },
716    { "text/plain", 0, 0 },
717    { "text/uri-list", 0, 0 },
718  };
719
720  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
721
722  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
723                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
724}
725
726static void
727readinitialprefs( struct cbdata * cbdata )
728{
729    int prefs[] =
730    {
731        PREF_ID_PORT,
732        PREF_ID_USEDOWNLIMIT,
733        PREF_ID_USEUPLIMIT,
734        PREF_ID_NAT,
735        PREF_ID_ICON,
736        PREF_ID_PEX,
737    };
738    int ii;
739
740    for( ii = 0; ALEN( prefs ) > ii; ii++ )
741    {
742        prefschanged( NULL, prefs[ii], cbdata );
743    }
744}
745
746static void
747prefschanged( GtkWidget * widget SHUTUP, int id, gpointer data )
748{
749    struct cbdata * cbdata;
750    tr_handle_t   * tr;
751    int             num;
752    gboolean        boolval;
753
754    cbdata = data;
755    tr     = tr_backend_handle( cbdata->back );
756
757    switch( id )
758    {
759        case PREF_ID_PORT:
760            tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
761            break;
762
763        case PREF_ID_USEDOWNLIMIT:
764        case PREF_ID_DOWNLIMIT:
765            num = -1;
766            if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) )
767            {
768                num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT );
769            }
770            tr_setGlobalDownloadLimit( tr, num );
771            break;
772
773        case PREF_ID_USEUPLIMIT:
774        case PREF_ID_UPLIMIT:
775            num = -1;
776            if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) )
777            {
778                num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT );
779            }
780            tr_setGlobalUploadLimit( tr, num );
781            break;
782
783        case PREF_ID_NAT:
784            tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
785            break;
786
787        case PREF_ID_ICON:
788            if( tr_prefs_get_bool_with_default( id ) )
789            {
790                makeicon( cbdata );
791            }
792            else if( NULL != cbdata->icon )
793            {
794                g_object_unref( cbdata->icon );
795                cbdata->icon = NULL;
796            }
797            break;
798
799        case PREF_ID_PEX:
800            boolval = tr_prefs_get_bool_with_default( id );
801            tr_torrentIterate( tr, setpex, &boolval );
802            break;
803
804        case PREF_ID_DIR:
805        case PREF_ID_ASKDIR:
806        case PREF_ID_ADDSTD:
807        case PREF_ID_ADDIPC:
808        case PREF_ID_MSGLEVEL:
809        case PREF_MAX_ID:
810            break;
811    }
812}
813
814void
815setpex( tr_torrent_t * tor, void * arg )
816{
817    gboolean * val;
818
819    val = arg;
820    tr_torrentDisablePex( tor, !(*val) );
821}
822
823gboolean
824updatemodel(gpointer gdata) {
825  struct cbdata *data = gdata;
826  TrTorrent *tor;
827  tr_stat_t *st;
828  GtkTreeIter iter;
829  float up, down;
830
831  if( !data->closing && 0 < global_sigcount )
832  {
833      wannaquit( data );
834      return FALSE;
835  }
836
837  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
838    do {
839      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
840      st = tr_torrent_stat(tor);
841      g_object_unref(tor);
842      /* XXX find out if setting the same data emits changed signal */
843      gtk_list_store_set( GTK_LIST_STORE( data->model ), &iter,
844          MC_STAT,   st->status,               MC_ERR,     st->error,
845          MC_TERR,   st->errorString,          MC_PROG,    st->progress,
846          MC_DRATE,  st->rateDownload,         MC_URATE,   st->rateUpload,
847          MC_ETA,    st->eta,                  MC_PEERS,   st->peersTotal,
848          MC_UPEERS, st->peersUploading,       MC_DPEERS,  st->peersDownloading,
849          MC_SEED,   st->seeders,              MC_LEECH,   st->leechers,
850          MC_DONE,   st->completedFromTracker, MC_TRACKER, st->tracker,
851          MC_DOWN,   st->downloaded,           MC_UP,      st->uploaded,
852          MC_LEFT,   st->left, -1);
853    } while(gtk_tree_model_iter_next(data->model, &iter));
854  }
855
856  /* update the main window's statusbar and toolbar buttons */
857  if( NULL != data->wind )
858  {
859      tr_torrentRates( tr_backend_handle( data->back ), &down, &up );
860      tr_window_update( TR_WINDOW( data->wind ), down, up );
861  }
862
863  /* check for politely stopped torrents unless we're exiting */
864  if( !data->closing )
865  {
866      tr_backend_torrents_stopped( data->back, FALSE );
867  }
868
869  /* update the message window */
870  msgwin_update();
871
872  return TRUE;
873}
874
875static void
876boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
877  gboolean *preachy_gcc = gdata;
878 
879  *preachy_gcc = FALSE;
880}
881
882static void
883windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
884{
885    g_assert( 0 <= action );
886    handleaction( gdata, action );
887}
888
889/* returns a GList containing a GtkTreeRowReference to each selected row */
890static GList *
891getselection( struct cbdata * cbdata )
892{
893    GtkTreeSelection    * sel;
894    GList               * rows, * ii;
895    GtkTreeRowReference * ref;
896
897    if( NULL == cbdata->wind )
898    {
899        return NULL;
900    }
901    g_object_get( cbdata->wind, "selection", &sel, NULL );
902    rows = gtk_tree_selection_get_selected_rows( sel, NULL );
903    for( ii = rows; NULL != ii; ii = ii->next )
904    {
905        ref = gtk_tree_row_reference_new( cbdata->model, ii->data );
906        gtk_tree_path_free( ii->data );
907        ii->data = ref;
908    }
909
910    return rows;
911}
912
913static void
914handleaction( struct cbdata * data, int act )
915{
916  GList *rows, *ii;
917  GtkTreePath *path;
918  GtkTreeIter iter;
919  TrTorrent *tor;
920  int status;
921  gboolean changed;
922  GtkWidget * win;
923
924  g_assert( ACTION_COUNT > act );
925
926  switch( act )
927  {
928      case ACT_OPEN:
929          makeaddwind( data->wind, addtorrents, data );
930          return;
931      case ACT_PREF:
932          if( NULL != data->prefs )
933          {
934              return;
935          }
936          data->prefs = tr_prefs_new_with_parent( data->wind );
937          g_signal_connect( data->prefs, "prefs-changed",
938                            G_CALLBACK( prefschanged ), data );
939          g_signal_connect( data->prefs, "destroy",
940                            G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
941          gtk_widget_show( GTK_WIDGET( data->prefs ) );
942          return;
943      case ACT_DEBUG:
944          if( !data->msgwinopen )
945          {
946              data->msgwinopen = TRUE;
947              win = msgwin_create();
948              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
949                                &data->msgwinopen );
950          }
951          return;
952      case ACT_ICON:
953          remakewind( data );
954          return;
955      case ACT_CLOSE:
956          if( NULL != data->wind )
957          {
958              winclose( NULL, NULL, data );
959          }
960          return;
961      case ACT_QUIT:
962          askquit( data->wind, wannaquit, data );
963          return;
964      case ACT_SEPARATOR1:
965      case ACT_SEPARATOR2:
966      case ACT_SEPARATOR3:
967          return;
968      case ACT_START:
969      case ACT_STOP:
970      case ACT_DELETE:
971      case ACT_INFO:
972      case ACTION_COUNT:
973          break;
974  }
975
976  /* get a list of references to selected rows */
977  rows = getselection( data );
978
979  changed = FALSE;
980  for(ii = rows; NULL != ii; ii = ii->next) {
981    if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
982       gtk_tree_model_get_iter(data->model, &iter, path)) {
983      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
984                         MC_STAT, &status, -1);
985      if( ACT_ISAVAIL( actions[act].flags, status ) )
986
987      {
988          switch( act )
989          {
990              case ACT_START:
991                  tr_torrentStart( tr_torrent_handle( tor ) );
992                  changed = TRUE;
993                  break;
994              case ACT_STOP:
995                  tr_torrentStop( tr_torrent_handle( tor ) );
996                  changed = TRUE;
997                  break;
998              case ACT_DELETE:
999                  /* tor will be unref'd in the politely_stopped handler */
1000                  g_object_ref( tor );
1001                  tr_torrent_stop_politely( tor );
1002                  if( TR_FLAG_SAVE & tr_torrent_info( tor )->flags )
1003                  {
1004                      tr_torrentRemoveSaved( tr_torrent_handle( tor ) );
1005                  }
1006                  gtk_list_store_remove( GTK_LIST_STORE( data->model ),
1007                                         &iter );
1008                  changed = TRUE;
1009                  break;
1010              case ACT_INFO:
1011                  makeinfowind( data->wind, data->model, path, tor );
1012                  break;
1013              case ACT_OPEN:
1014              case ACT_PREF:
1015              case ACT_DEBUG:
1016              case ACT_ICON:
1017              case ACT_CLOSE:
1018              case ACT_QUIT:
1019              case ACT_SEPARATOR1:
1020              case ACT_SEPARATOR2:
1021              case ACT_SEPARATOR3:
1022              case ACTION_COUNT:
1023                  break;
1024          }
1025      }
1026      g_object_unref(tor);
1027    }
1028    if(NULL != path)
1029      gtk_tree_path_free(path);
1030    gtk_tree_row_reference_free(ii->data);
1031  }
1032  g_list_free(rows);
1033
1034  if(changed) {
1035    savetorrents(data);
1036    updatemodel(data);
1037  }
1038}
1039
1040static void
1041addtorrents(void *vdata, void *state, GList *files,
1042            const char *dir, guint flags) {
1043  struct cbdata *data = vdata;
1044  GList *torlist, *errlist, *ii;
1045  char *errstr;
1046  TrTorrent *tor;
1047  GtkTreeIter iter;
1048  const char * pref;
1049  tr_info_t *in;
1050
1051  errlist = NULL;
1052  torlist = NULL;
1053
1054  if( NULL != state )
1055  {
1056      torlist = tr_backend_load_state( data->back, state, flags, &errlist );
1057  }
1058
1059  if(NULL != files) {
1060    if( NULL == dir )
1061    {
1062        pref = tr_prefs_get( PREF_ID_ASKDIR );
1063        if( NULL != pref && strbool( pref ) )
1064        {
1065            promptfordir( data->wind, addtorrents, data, files, flags );
1066            files = NULL;
1067        }
1068        dir = getdownloaddir();
1069    }
1070    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
1071      errstr = NULL;
1072      tor = tr_torrent_new(G_OBJECT(data->back), ii->data, dir,
1073                           flags, &errstr);
1074      if(NULL != tor)
1075        torlist = g_list_append(torlist, tor);
1076      if(NULL != errstr)
1077        errlist = g_list_append(errlist, errstr);
1078    }
1079  }
1080
1081  for( ii = g_list_first( torlist ); NULL != ii; ii = ii->next )
1082  {
1083      gtk_list_store_append( GTK_LIST_STORE( data->model ), &iter );
1084      in = tr_torrent_info( ii->data );
1085      gtk_list_store_set( GTK_LIST_STORE( data->model ), &iter,
1086                          MC_NAME,    in->name,
1087                          MC_SIZE,    in->totalSize,
1088                          MC_TORRENT, ii->data, -1);
1089      /* we will always ref a torrent before politely stopping it */
1090      g_signal_connect( ii->data, "politely_stopped",
1091                        G_CALLBACK( g_object_unref ), data );
1092      g_object_unref( ii->data );
1093  }
1094
1095  if(NULL != errlist) {
1096    errstr = joinstrlist(errlist, "\n");
1097    errmsg( data->wind, ngettext( "Failed to load torrent file:\n%s",
1098                                  "Failed to load torrent files:\n%s",
1099                                  g_list_length( errlist ) ), errstr );
1100    g_list_foreach(errlist, (GFunc)g_free, NULL);
1101    g_list_free(errlist);
1102    g_free(errstr);
1103  }
1104
1105  if(NULL != torlist) {
1106    updatemodel(data);
1107    savetorrents(data);
1108  }
1109}
1110
1111static void
1112savetorrents( struct cbdata *data )
1113{
1114    char * errstr;
1115
1116    tr_backend_save_state( data->back, &errstr );
1117    if( NULL != errstr )
1118    {
1119        errmsg( data->wind, "%s", errstr );
1120        g_free( errstr );
1121    }
1122}
1123
1124static void
1125safepipe(void) {
1126  struct sigaction sa;
1127
1128  bzero(&sa, sizeof(sa));
1129  sa.sa_handler = SIG_IGN;
1130  sigaction(SIGPIPE, &sa, NULL);
1131}
1132
1133static void
1134setupsighandlers(void) {
1135  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1136  struct sigaction sa;
1137  int ii;
1138
1139  bzero(&sa, sizeof(sa));
1140  sa.sa_handler = fatalsig;
1141  for(ii = 0; ii < ALEN(sigs); ii++)
1142    sigaction(sigs[ii], &sa, NULL);
1143}
1144
1145static void
1146fatalsig(int sig) {
1147  struct sigaction sa;
1148
1149  if(SIGCOUNT_MAX <= ++global_sigcount) {
1150    bzero(&sa, sizeof(sa));
1151    sa.sa_handler = SIG_DFL;
1152    sigaction(sig, &sa, NULL);
1153    raise(sig);
1154  }
1155}
Note: See TracBrowser for help on using the repository browser.