source: trunk/gtk/main.c @ 2185

Last change on this file since 2185 was 2185, checked in by charles, 15 years ago

add color-coded lines to the gtk debug window -- info is black, debug is grey, errors are red...

  • Property svn:keywords set to Date Rev Author Id
File size: 32.3 KB
Line 
1/******************************************************************************
2 * $Id: main.c 2185 2007-06-22 20:59:23Z charles $
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 "actions.h"
40#include "conf.h"
41#include "dialogs.h"
42#include "ipc.h"
43#include "makemeta-ui.h"
44#include "msgwin.h"
45#include "torrent-inspector.h"
46#include "tr_cell_renderer_progress.h"
47#include "tr_core.h"
48#include "tr_icon.h"
49#include "tr_prefs.h"
50#include "tr_torrent.h"
51#include "tr_window.h"
52#include "util.h"
53#include "ui.h"
54
55#include "transmission.h"
56#include "version.h"
57
58/* time in seconds to wait for torrents to stop when exiting */
59#define TRACKER_EXIT_TIMEOUT    10
60
61/* interval in milliseconds to update the torrent list display */
62#define UPDATE_INTERVAL         1000
63
64/* interval in milliseconds to check for stopped torrents and update display */
65#define EXIT_CHECK_INTERVAL     500
66
67/* number of fatal signals required to cause an immediate exit */
68#define SIGCOUNT_MAX            3
69
70#if GTK_CHECK_VERSION(2,8,0)
71#define SHOW_LICENSE
72static const char * LICENSE = 
73"The Transmission binaries and most of its source code is distributed "
74"license. "
75"\n\n"
76"Some files are copyrighted by Charles Kerr and are covered by "
77"the GPL version 2.  Works owned by the Transmission project "
78"are granted a special exemption to clause 2(b) so that the bulk "
79"of its code can remain under the MIT license.  This exemption does "
80"not extend to original or derived works not owned by the "
81"Transmission project. "
82"\n\n"
83"Permission is hereby granted, free of charge, to any person obtaining "
84"a copy of this software and associated documentation files (the "
85"'Software'), to deal in the Software without restriction, including "
86"without limitation the rights to use, copy, modify, merge, publish, "
87"distribute, sublicense, and/or sell copies of the Software, and to "
88"permit persons to whom the Software is furnished to do so, subject to "
89"the following conditions: "
90"\n\n"
91"The above copyright notice and this permission notice shall be included "
92"in all copies or substantial portions of the Software. "
93"\n\n"
94"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
95"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
96"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
97"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
98"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
99"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
100"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
101#endif
102
103struct cbdata {
104    GtkWindow    * wind;
105    TrCore       * core;
106    GtkWidget    * icon;
107    TrPrefs      * prefs;
108    guint          timer;
109    gboolean       msgwinopen;
110    gboolean       closing;
111    GList        * errqueue;
112};
113
114struct exitdata {
115    struct cbdata * cbdata;
116    time_t          started;
117    guint           timer;
118};
119
120#define CBDATA_PTR              "callback-data-pointer"
121
122static GtkUIManager * myUIManager = NULL;
123
124static sig_atomic_t global_sigcount = 0;
125
126static GList *
127readargs( int argc, char ** argv, gboolean * sendquit, gboolean * paused );
128static gboolean
129sendremote( GList * files, gboolean sendquit );
130static void
131gtksetup( int * argc, char *** argv, struct cbdata* );
132static void
133appsetup( TrWindow * wind, benc_val_t * state, GList * args,
134          struct cbdata * , gboolean paused );
135static void
136winsetup( struct cbdata * cbdata, TrWindow * wind );
137static void
138makeicon( struct cbdata * cbdata );
139static void
140wannaquit( void * vdata );
141static gboolean
142exitcheck(gpointer gdata);
143static void
144setupdrag(GtkWidget *widget, struct cbdata *data);
145static void
146gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
147        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
148
149static void
150coreerr( TrCore * core, enum tr_core_err code, const char * msg,
151         gpointer gdata );
152static void
153coreprompt( TrCore *, GList *, enum tr_torrent_action, gboolean, gpointer );
154static void
155corepromptdata( TrCore *, uint8_t *, size_t, gboolean, gpointer );
156static void
157readinitialprefs( struct cbdata * cbdata );
158static void
159prefschanged( TrCore * core, int id, gpointer data );
160static void
161setpex( tr_torrent_t * tor, void * arg );
162static gboolean
163updatemodel(gpointer gdata);
164static void
165boolwindclosed(GtkWidget *widget, gpointer gdata);
166static GList *
167getselection( struct cbdata * cbdata );
168
169static void
170safepipe(void);
171static void
172setupsighandlers(void);
173static void
174fatalsig(int sig);
175
176static void
177accumulateStatusForeach (GtkTreeModel * model,
178                         GtkTreePath  * path UNUSED,
179                         GtkTreeIter  * iter,
180                         gpointer       accumulated_status)
181{
182    int status = 0;
183    gtk_tree_model_get( model, iter, MC_STAT, &status, -1 );
184    *(int*)accumulated_status |= status;
185}
186
187static void
188refreshTorrentActions( GtkTreeSelection * s )
189{
190    int status = 0;
191    gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &status );
192    action_sensitize( "stop-torrent", (status & TR_STATUS_ACTIVE) != 0);
193    action_sensitize( "start-torrent", (status & TR_STATUS_INACTIVE) != 0);
194    action_sensitize( "remove-torrent", status != 0);
195    action_sensitize( "recheck-torrent", status != 0);
196    action_sensitize( "show-torrent-inspector", status != 0);
197}
198
199static void
200selectionChangedCB( GtkTreeSelection * s, gpointer unused UNUSED )
201{
202    refreshTorrentActions( s );
203}
204
205int
206main( int argc, char ** argv )
207{
208    struct cbdata * cbdata = g_new (struct cbdata, 1);
209    char       * err;
210    benc_val_t * state;
211    GList      * argfiles;
212    gboolean     didinit, didlock, sendquit, startpaused;
213
214    safepipe();                 /* ignore SIGPIPE */
215    argfiles = readargs( argc, argv, &sendquit, &startpaused );
216    didinit = cf_init( tr_getPrefsDirectory(), NULL );
217    didlock = FALSE;
218    if( didinit )
219    {
220        /* maybe send remote commands, also try cf_lock() */
221        didlock = sendremote( argfiles, sendquit );
222    }
223    setupsighandlers();         /* set up handlers for fatal signals */
224    gtksetup( &argc, &argv, cbdata );   /* set up gtk and gettext */
225
226    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
227        ( didlock || cf_lock( &err ) ) )
228    {
229        GtkWindow  * mainwind;
230
231        /* create main window now to be a parent to any error dialogs */
232        mainwind = GTK_WINDOW( tr_window_new( myUIManager ) );
233
234        /* try to load prefs and saved state */
235        cf_loadprefs( &err );
236        if( NULL != err )
237        {
238            errmsg( mainwind, "%s", err );
239            g_free( err );
240        }
241        state = cf_loadstate( &err );
242        if( NULL != err )
243        {
244            errmsg( mainwind, "%s", err );
245            g_free( err );
246        }
247
248        msgwin_loadpref();      /* set message level here before tr_init() */
249        appsetup( mainwind, state, argfiles, cbdata, startpaused );
250        cf_freestate( state );
251    }
252    else
253    {
254        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
255                                      NULL, "%s", err ) );
256        g_free( err );
257    }
258
259    freestrlist(argfiles);
260
261    gtk_main();
262
263    return 0;
264}
265
266GList *
267readargs( int argc, char ** argv, gboolean * sendquit, gboolean * startpaused )
268{
269    struct option opts[] =
270    {
271        { "help",    no_argument, NULL, 'h' },
272        { "paused",  no_argument, NULL, 'p' },
273        { "quit",    no_argument, NULL, 'q' },
274        { "version", no_argument, NULL, 'v' },
275        { NULL, 0, NULL, 0 }
276    };
277    int          opt;
278    const char * name;
279
280    *sendquit    = FALSE;
281    *startpaused = FALSE;
282
283    gtk_parse_args( &argc, &argv );
284    name = g_get_prgname();
285
286    while( 0 <= ( opt = getopt_long( argc, argv, "hpqv", opts, NULL ) ) )
287    {
288        switch( opt )
289        {
290            case 'p':
291                *startpaused = TRUE;
292                break;
293            case 'q':
294                *sendquit = TRUE;
295                break;
296            case 'v':
297            case 'h':
298                printf(
299_("usage: %s [-hpq] [files...]\n"
300  "\n"
301  "Transmission %s (r%d) http://transmission.m0k.org/\n"
302  "A free, lightweight BitTorrent client with a simple, intuitive interface\n"
303  "\n"
304  "  -h --help    display this message and exit\n"
305  "  -p --paused  start with all torrents paused\n"
306  "  -q --quit    request that the running %s instance quit\n"
307  "\n"
308  "Only one instance of %s may run at one time. Multiple\n"
309  "torrent files may be loaded at startup by adding them to the command\n"
310  "line. If %s is already running, those torrents will be\n"
311  "opened in the running instance.\n"),
312                        name, VERSION_STRING, VERSION_REVISION,
313                        name, name, name );
314                exit(0);
315                break;
316        }
317    }
318
319    argc -= optind;
320    argv += optind;
321
322    return checkfilenames( argc, argv );
323}
324
325static gboolean
326sendremote( GList * files, gboolean sendquit )
327{
328    gboolean didlock;
329
330    didlock = cf_lock( NULL );
331
332    if( NULL != files )
333    {
334        /* send files if there's another instance, otherwise start normally */
335        if( !didlock )
336        {
337            exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
338        }
339    }
340
341    if( sendquit )
342    {
343        /* either send a quit message or exit if no other instance */
344        if( !didlock )
345        {
346            exit( ipc_sendquit_blocking() ? 0 : 1 );
347        }
348        exit( 0 );
349    }
350
351    return didlock;
352}
353
354static void
355gtksetup( int * argc, char *** argv, struct cbdata * callback_data )
356{
357
358    bindtextdomain( "transmission-gtk", LOCALEDIR );
359    bind_textdomain_codeset( "transmission-gtk", "UTF-8" );
360    textdomain( "transmission-gtk" );
361
362    g_set_application_name( _("Transmission") );
363    gtk_init( argc, argv );
364
365    /* connect up the actions */
366    myUIManager = gtk_ui_manager_new ();
367    actions_init ( myUIManager, callback_data );
368    gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
369    gtk_ui_manager_ensure_update (myUIManager);
370
371    /* tweak some style properties in dialogs to get closer to the GNOME HiG */
372    gtk_rc_parse_string(
373        "style \"transmission-standard\"\n"
374        "{\n"
375        "    GtkDialog::action-area-border  = 6\n"
376        "    GtkDialog::button-spacing      = 12\n"
377        "    GtkDialog::content-area-border = 6\n"
378        "}\n"
379        "widget \"TransmissionDialog\" style \"transmission-standard\"\n" );
380
381    gtk_window_set_default_icon_name ( "ICON_TRANSMISSION" );
382}
383
384static void
385appsetup( TrWindow * wind, benc_val_t * state, GList * args,
386          struct cbdata * cbdata, gboolean paused )
387{
388    enum tr_torrent_action action;
389
390    /* fill out cbdata */
391    cbdata->wind       = NULL;
392    cbdata->core       = tr_core_new();
393    cbdata->icon       = NULL;
394    cbdata->prefs      = NULL;
395    cbdata->timer      = 0;
396    cbdata->msgwinopen = FALSE;
397    cbdata->closing    = FALSE;
398    cbdata->errqueue   = NULL;
399
400    /* set up core handlers */
401    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
402    g_signal_connect( cbdata->core, "directory-prompt",
403                      G_CALLBACK( coreprompt ), cbdata );
404    g_signal_connect( cbdata->core, "directory-prompt-data",
405                      G_CALLBACK( corepromptdata ), cbdata );
406    g_signal_connect_swapped( cbdata->core, "quit",
407                              G_CALLBACK( wannaquit ), cbdata );
408    g_signal_connect( cbdata->core, "prefs-changed",
409                      G_CALLBACK( prefschanged ), cbdata );
410
411    /* apply a few prefs */
412    readinitialprefs( cbdata );
413
414    /* add torrents from command-line and saved state */
415    if( NULL != state )
416    {
417        tr_core_load( cbdata->core, state, paused );
418    }
419    if( NULL != args )
420    {
421        action = toraddaction( tr_prefs_get( PREF_ID_ADDIPC ) );
422        tr_core_add_list( cbdata->core, args, action, paused );
423    }
424    tr_core_torrents_added( cbdata->core );
425
426    /* set up the ipc socket */
427    ipc_socket_setup( GTK_WINDOW( wind ), cbdata->core );
428
429    /* set up main window */
430    winsetup( cbdata, wind );
431
432    /* start model update timer */
433    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
434    updatemodel( cbdata );
435
436    /* show the window */
437    gtk_widget_show( GTK_WIDGET(wind) );
438}
439
440static gboolean
441winclose( GtkWidget * widget UNUSED, GdkEvent * event UNUSED, gpointer gdata )
442{
443    struct cbdata * cbdata = (struct cbdata *) gdata;
444
445    if( cbdata->icon != NULL )
446        gtk_widget_hide( GTK_WIDGET( cbdata->wind ) );
447    else
448        askquit( cbdata->wind, wannaquit, cbdata );
449
450    return TRUE; /* don't propagate event further */
451}
452
453static void
454rowChangedCB( GtkTreeModel  * model UNUSED,
455              GtkTreePath   * path UNUSED,
456              GtkTreeIter   * iter UNUSED,
457              gpointer        sel)
458{
459    refreshTorrentActions( GTK_TREE_SELECTION(sel) );
460}
461
462static void
463winsetup( struct cbdata * cbdata, TrWindow * wind )
464{
465    GtkTreeModel * model;
466    GtkTreeSelection * sel;
467
468    g_assert( NULL == cbdata->wind );
469    cbdata->wind = GTK_WINDOW( wind );
470
471    sel = tr_window_get_selection( cbdata->wind );
472    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
473    selectionChangedCB( sel, NULL );
474    model = tr_core_model( cbdata->core );
475    gtk_tree_view_set_model ( gtk_tree_selection_get_tree_view(sel), model );
476    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
477    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
478   
479    setupdrag( GTK_WIDGET(wind), cbdata );
480}
481
482static void
483makeicon( struct cbdata * cbdata )
484{
485    if( NULL == cbdata->icon )
486        cbdata->icon = tr_icon_new( );
487}
488
489static void
490wannaquit( void * vdata )
491{
492  struct cbdata * data;
493  struct exitdata *edata;
494
495  data = vdata;
496  if( data->closing )
497  {
498      return;
499  }
500  data->closing = TRUE;
501
502  /* stop the update timer */
503  if(0 < data->timer)
504    g_source_remove(data->timer);
505  data->timer = 0;
506
507  /* pause torrents and stop nat traversal */
508  tr_core_shutdown( data->core );
509
510  /* set things up to wait for torrents to stop */
511  edata = g_new0(struct exitdata, 1);
512  edata->cbdata = data;
513  edata->started = time(NULL);
514  /* check if torrents are still running */
515  if(exitcheck(edata)) {
516    /* yes, start the exit timer and disable widgets */
517    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
518    if( NULL != data->wind )
519    {
520        gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
521    }
522  }
523}
524
525static gboolean
526exitcheck( gpointer gdata )
527{
528    struct exitdata    * edata;
529    struct cbdata      * cbdata;
530
531    edata  = gdata;
532    cbdata = edata->cbdata;
533
534    /* keep waiting until we're ready to quit or we hit the exit timeout */
535    if( time( NULL ) - edata->started < TRACKER_EXIT_TIMEOUT )
536    {
537        if( !tr_core_quiescent( cbdata->core ) )
538        {
539            updatemodel( cbdata );
540            return TRUE;
541        }
542    }
543
544    /* exit otherwise */
545    if( 0 < edata->timer )
546    {
547        g_source_remove( edata->timer );
548    }
549    g_free( edata );
550    /* note that cbdata->prefs holds a reference to cbdata->core, and
551       it's destruction may trigger callbacks that use cbdata->core */
552    if( NULL != cbdata->prefs )
553    {
554        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
555    }
556    if( NULL != cbdata->wind )
557    {
558        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
559    }
560    g_object_unref( cbdata->core );
561    if( NULL != cbdata->icon )
562    {
563        g_object_unref( cbdata->icon );
564    }
565    g_assert( 0 == cbdata->timer );
566    if( NULL != cbdata->errqueue )
567    {
568        g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
569        g_list_free( cbdata->errqueue );
570    }
571    g_free( cbdata );
572    gtk_main_quit();
573
574    return FALSE;
575}
576
577static void
578gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
579        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
580        gpointer gdata) {
581  struct cbdata *data = gdata;
582  char prefix[] = "file:";
583  char *files, *decoded, *deslashed, *hostless;
584  int ii, len;
585  GList *errs;
586  struct stat sb;
587  int prelen = strlen(prefix);
588  GList *paths, *freeables;
589  enum tr_torrent_action action;
590
591#ifdef DND_DEBUG
592  char *sele = gdk_atom_name(sel->selection);
593  char *targ = gdk_atom_name(sel->target);
594  char *type = gdk_atom_name(sel->type);
595
596  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
597          sele, targ, type, sel->format, sel->length);
598  g_free(sele);
599  g_free(targ);
600  g_free(type);
601  if(8 == sel->format) {
602    for(ii = 0; ii < sel->length; ii++)
603      fprintf(stderr, "%02X ", sel->data[ii]);
604    fprintf(stderr, "\n");
605  }
606#endif
607
608  errs = NULL;
609  paths = NULL;
610  freeables = NULL;
611  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
612     8 == sel->format) {
613    /* split file list on carriage returns and linefeeds */
614    files = g_new(char, sel->length + 1);
615    memcpy(files, sel->data, sel->length);
616    files[sel->length] = '\0';
617    for(ii = 0; '\0' != files[ii]; ii++)
618      if('\015' == files[ii] || '\012' == files[ii])
619        files[ii] = '\0';
620
621    /* try to get a usable filename out of the URI supplied and add it */
622    for(ii = 0; ii < sel->length; ii += len + 1) {
623      if('\0' == files[ii])
624        len = 0;
625      else {
626        len = strlen(files + ii);
627        /* de-urlencode the URI */
628        decoded = urldecode(files + ii, len);
629        freeables = g_list_append(freeables, decoded);
630        if(g_utf8_validate(decoded, -1, NULL)) {
631          /* remove the file: prefix */
632          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
633            deslashed = decoded + prelen;
634            /* trim excess / characters from the beginning */
635            while('/' == deslashed[0] && '/' == deslashed[1])
636              deslashed++;
637            /* if the file doesn't exist, the first part might be a hostname */
638            if(0 > g_stat(deslashed, &sb) &&
639               NULL != (hostless = strchr(deslashed + 1, '/')) &&
640               0 == g_stat(hostless, &sb))
641              deslashed = hostless;
642            /* finally, add it to the list of torrents to try adding */
643            paths = g_list_append(paths, deslashed);
644          }
645        }
646      }
647    }
648
649    /* try to add any torrents we found */
650    if( NULL != paths )
651    {
652        action = toraddaction( tr_prefs_get( PREF_ID_ADDSTD ) );
653        tr_core_add_list( data->core, paths, action, FALSE );
654        tr_core_torrents_added( data->core );
655        g_list_free(paths);
656    }
657    freestrlist(freeables);
658    g_free(files);
659  }
660
661  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
662}
663
664static void
665setupdrag(GtkWidget *widget, struct cbdata *data) {
666  GtkTargetEntry targets[] = {
667    { "STRING",     0, 0 },
668    { "text/plain", 0, 0 },
669    { "text/uri-list", 0, 0 },
670  };
671
672  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
673
674  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
675                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
676}
677
678static void
679coreerr( TrCore * core SHUTUP, enum tr_core_err code, const char * msg,
680         gpointer gdata )
681{
682    struct cbdata * cbdata = gdata;
683    char          * joined;
684
685    switch( code )
686    {
687        case TR_CORE_ERR_ADD_TORRENT:
688            cbdata->errqueue = g_list_append( cbdata->errqueue,
689                                              g_strdup( msg ) );
690            return;
691        case TR_CORE_ERR_NO_MORE_TORRENTS:
692            if( NULL != cbdata->errqueue )
693            {
694                joined = joinstrlist( cbdata->errqueue, "\n" );
695                errmsg( cbdata->wind,
696                        ngettext( "Failed to load torrent file:\n%s",
697                                  "Failed to load torrent files:\n%s",
698                                  g_list_length( cbdata->errqueue ) ),
699                        joined );
700                g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
701                g_list_free( cbdata->errqueue );
702                cbdata->errqueue = NULL;
703                g_free( joined );
704            }
705            return;
706        case TR_CORE_ERR_SAVE_STATE:
707            errmsg( cbdata->wind, "%s", msg );
708            return;
709    }
710
711    g_assert_not_reached();
712}
713
714void
715coreprompt( TrCore * core, GList * paths, enum tr_torrent_action act,
716            gboolean paused, gpointer gdata )
717{
718    struct cbdata * cbdata = gdata;
719
720    promptfordir( cbdata->wind, core, paths, NULL, 0, act, paused );
721}
722
723void
724corepromptdata( TrCore * core, uint8_t * data, size_t size,
725                gboolean paused, gpointer gdata )
726{
727    struct cbdata * cbdata = gdata;
728
729    promptfordir( cbdata->wind, core, NULL, data, size, TR_TOR_LEAVE, paused );
730}
731
732static void
733readinitialprefs( struct cbdata * cbdata )
734{
735    int prefs[] =
736    {
737        PREF_ID_PORT,
738        PREF_ID_USEDOWNLIMIT,
739        PREF_ID_USEUPLIMIT,
740        PREF_ID_NAT,
741        PREF_ID_ICON,
742        PREF_ID_PEX,
743    };
744    int ii;
745
746    for( ii = 0; ALEN( prefs ) > ii; ii++ )
747    {
748        prefschanged( NULL, prefs[ii], cbdata );
749    }
750}
751
752static void
753prefschanged( TrCore * core SHUTUP, int id, gpointer data )
754{
755    struct cbdata * cbdata;
756    tr_handle_t   * tr;
757    int             num;
758    gboolean        boolval;
759
760    cbdata = data;
761    tr     = tr_core_handle( cbdata->core );
762
763    switch( id )
764    {
765        case PREF_ID_PORT:
766            tr_setBindPort( tr, tr_prefs_get_int_with_default( id ) );
767            break;
768
769        case PREF_ID_USEDOWNLIMIT:
770        case PREF_ID_DOWNLIMIT:
771            num = -1;
772            if( tr_prefs_get_bool_with_default( PREF_ID_USEDOWNLIMIT ) )
773            {
774                num = tr_prefs_get_int_with_default( PREF_ID_DOWNLIMIT );
775            }
776            tr_setGlobalDownloadLimit( tr, num );
777            break;
778
779        case PREF_ID_USEUPLIMIT:
780        case PREF_ID_UPLIMIT:
781            num = -1;
782            if( tr_prefs_get_bool_with_default( PREF_ID_USEUPLIMIT ) )
783            {
784                num = tr_prefs_get_int_with_default( PREF_ID_UPLIMIT );
785            }
786            tr_setGlobalUploadLimit( tr, num );
787            break;
788
789        case PREF_ID_NAT:
790            tr_natTraversalEnable( tr, tr_prefs_get_bool_with_default( id ) );
791            break;
792
793        case PREF_ID_ICON:
794            if( tr_prefs_get_bool_with_default( id ) )
795            {
796                makeicon( cbdata );
797            }
798            else if( NULL != cbdata->icon )
799            {
800g_message ("foo");
801                g_object_unref( cbdata->icon );
802                cbdata->icon = NULL;
803            }
804            break;
805
806        case PREF_ID_PEX:
807            boolval = tr_prefs_get_bool_with_default( id );
808            tr_torrentIterate( tr, setpex, &boolval );
809            break;
810
811        case PREF_ID_DIR:
812        case PREF_ID_ASKDIR:
813        case PREF_ID_ADDSTD:
814        case PREF_ID_ADDIPC:
815        case PREF_ID_MSGLEVEL:
816        case PREF_MAX_ID:
817            break;
818    }
819}
820
821void
822setpex( tr_torrent_t * tor, void * arg )
823{
824    gboolean * val;
825
826    val = arg;
827    tr_torrentDisablePex( tor, !(*val) );
828}
829
830gboolean
831updatemodel(gpointer gdata) {
832  struct cbdata *data = gdata;
833  float up, down;
834
835  if( !data->closing && 0 < global_sigcount )
836  {
837      wannaquit( data );
838      return FALSE;
839  }
840
841  /* update the torrent data in the model */
842  tr_core_update( data->core );
843
844  /* update the main window's statusbar and toolbar buttons */
845  if( NULL != data->wind )
846  {
847      tr_torrentRates( tr_core_handle( data->core ), &down, &up );
848      tr_window_update( data->wind, down, up );
849  }
850
851  /* update the message window */
852  msgwin_update();
853
854  return TRUE;
855}
856
857static void
858boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
859  gboolean *preachy_gcc = gdata;
860 
861  *preachy_gcc = FALSE;
862}
863
864/* returns a GList containing a GtkTreeRowReference to each selected row */
865static GList *
866getselection( struct cbdata * cbdata )
867{
868    GList * rows = NULL;
869
870    if( NULL != cbdata->wind )
871    {
872        GList * ii;
873        GtkTreeSelection *s = tr_window_get_selection(cbdata->wind);
874        GtkTreeModel * model = tr_core_model( cbdata->core );
875        rows = gtk_tree_selection_get_selected_rows( s, NULL );
876        for( ii = rows; NULL != ii; ii = ii->next )
877        {
878            GtkTreeRowReference * ref = gtk_tree_row_reference_new(
879                model, ii->data );
880            gtk_tree_path_free( ii->data );
881            ii->data = ref;
882        }
883    }
884
885    return rows;
886}
887
888static void
889about ( void )
890{
891  char buf[128];
892  GtkWidget * w = gtk_about_dialog_new ();
893  GtkAboutDialog * a = GTK_ABOUT_DIALOG (w);
894  const char *authors[] = { "Eric Petit (Back-end; OS X)",
895                            "Josh Elsasser (Back-end; GTK+)",
896                            "Mitchell Livingston (Back-end; OS X)",
897                            "Charles Kerr (Back-end; GTK+)",
898                            "Bryan Varner (BeOS)", 
899                            NULL };
900  g_snprintf (buf, sizeof(buf), _("%s (%d)"), VERSION_STRING, VERSION_REVISION);
901  gtk_about_dialog_set_version (a, buf);
902#ifdef SHOW_LICENSE
903  gtk_about_dialog_set_license (a, LICENSE);
904  gtk_about_dialog_set_wrap_license (a, TRUE);
905#endif
906  gtk_about_dialog_set_website (a, "http://transmission.m0k.org/");
907  gtk_about_dialog_set_copyright (a, _("Copyright 2005-2007 The Transmission Project"));
908  gtk_about_dialog_set_authors (a, authors);
909  gtk_about_dialog_set_translator_credits (a, _("translator-credits"));
910  g_signal_connect_swapped (w, "response", G_CALLBACK (gtk_widget_destroy), w);
911  gtk_widget_show_all (w);
912}
913
914static void
915startTorrentForeach (GtkTreeModel * model,
916                     GtkTreePath  * path UNUSED,
917                     GtkTreeIter  * iter,
918                     gpointer       data UNUSED)
919{
920    TrTorrent * tor = NULL;
921    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
922    tr_torrent_start( tor );
923    g_object_unref( G_OBJECT( tor ) );
924}
925
926static void
927stopTorrentForeach (GtkTreeModel * model,
928                    GtkTreePath  * path UNUSED,
929                    GtkTreeIter  * iter,
930                    gpointer       data UNUSED)
931{
932    TrTorrent * tor = NULL;
933    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
934    tr_torrent_stop( tor );
935    g_object_unref( G_OBJECT( tor ) );
936}
937
938static void
939showInfoForeach (GtkTreeModel * model,
940                 GtkTreePath  * path UNUSED,
941                 GtkTreeIter  * iter,
942                 gpointer       data UNUSED)
943{
944    TrTorrent * tor = NULL;
945    GtkWidget * w;
946    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
947    w = torrent_inspector_new( GTK_WINDOW(data), tor );
948    gtk_widget_show( w );
949    g_object_unref( G_OBJECT( tor ) );
950}
951
952static void
953recheckTorrentForeach (GtkTreeModel * model,
954                       GtkTreePath  * path UNUSED,
955                       GtkTreeIter  * iter,
956                       gpointer       data UNUSED)
957{
958    TrTorrent * gtor = NULL;
959    int status = 0;
960    tr_torrent_t * tor;
961    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, MC_STAT, &status, -1 );
962    tor = tr_torrent_handle( gtor );
963    if( status & TR_STATUS_ACTIVE )
964        tr_torrentStop( tor );
965    tr_torrentRemoveFastResume( tor );
966    tr_torrentStart( tor );
967    g_object_unref( G_OBJECT( gtor ) );
968}
969
970void
971doAction ( const char * action_name, gpointer user_data )
972{
973    struct cbdata * data = (struct cbdata *) user_data;
974    gboolean changed = FALSE;
975
976    if (!strcmp (action_name, "add-torrent"))
977    {
978        makeaddwind( data->wind, data->core );
979    }
980    else if (!strcmp (action_name, "start-torrent"))
981    {
982        GtkTreeSelection * s = tr_window_get_selection(data->wind);
983        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
984        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
985    }
986    else if (!strcmp (action_name, "stop-torrent"))
987    {
988        GtkTreeSelection * s = tr_window_get_selection(data->wind);
989        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
990        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
991    }
992    else if (!strcmp (action_name, "recheck-torrent"))
993    {
994        GtkTreeSelection * s = tr_window_get_selection(data->wind);
995        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
996        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
997    }
998    else if (!strcmp (action_name, "show-torrent-inspector"))
999    {
1000        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1001        gtk_tree_selection_selected_foreach( s, showInfoForeach, data->wind );
1002    }
1003    else if (!strcmp (action_name, "create-torrent"))
1004    {
1005        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ), tr_core_handle( data->core ) );
1006        gtk_widget_show_all( w );
1007    }
1008    else if (!strcmp (action_name, "remove-torrent"))
1009    {
1010        /* this modifies the model's contents, so can't use foreach */
1011        GList *l, *sel = getselection( data );
1012        GtkTreeModel *model = tr_core_model( data->core );
1013        for( l=sel; l!=NULL; l=l->next )
1014        {
1015            GtkTreeIter iter;
1016            GtkTreeRowReference * reference = (GtkTreeRowReference *) l->data;
1017            GtkTreePath * path = gtk_tree_row_reference_get_path( reference );
1018            gtk_tree_model_get_iter( model, &iter, path );
1019            tr_core_delete_torrent( data->core, &iter );
1020            gtk_tree_row_reference_free( reference );
1021            changed = TRUE;
1022        }
1023        g_list_free( sel );
1024    }
1025    else if (!strcmp (action_name, "close"))
1026    {
1027        if( data->wind != NULL )
1028            winclose( NULL, NULL, data );
1029    }
1030    else if (!strcmp (action_name, "quit"))
1031    {
1032        askquit( data->wind, wannaquit, data );
1033    }
1034    else if (!strcmp (action_name, "edit-preferences"))
1035    {
1036        if( NULL == data->prefs )
1037        {
1038            data->prefs = tr_prefs_new_with_parent( G_OBJECT( data->core ),
1039                                                    data->wind );
1040            g_signal_connect( data->prefs, "destroy",
1041                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1042            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1043        }
1044    }
1045    else if (!strcmp (action_name, "show-debug-window"))
1046    {
1047        if( !data->msgwinopen )
1048        {
1049            GtkWidget * win = msgwin_create( data->core );
1050            g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
1051                                &data->msgwinopen );
1052            data->msgwinopen = TRUE;
1053        }
1054    }
1055    else if (!strcmp (action_name, "show-about-dialog"))
1056    {
1057        about();
1058    }
1059    else if (!strcmp (action_name, "toggle-main-window"))
1060    {
1061        GtkWidget * w = GTK_WIDGET (data->wind);
1062        if (GTK_WIDGET_VISIBLE(w))
1063            gtk_widget_hide (w);
1064        else
1065            gtk_window_present (GTK_WINDOW(w));
1066    }
1067    else g_error ("Unhandled action: %s", action_name );
1068
1069    if(changed)
1070    {
1071        updatemodel( data );
1072        tr_core_save( data->core );
1073    }
1074}
1075
1076
1077static void
1078safepipe(void) {
1079  struct sigaction sa;
1080
1081  bzero(&sa, sizeof(sa));
1082  sa.sa_handler = SIG_IGN;
1083  sigaction(SIGPIPE, &sa, NULL);
1084}
1085
1086static void
1087setupsighandlers(void) {
1088  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM};
1089  struct sigaction sa;
1090  int ii;
1091
1092  bzero(&sa, sizeof(sa));
1093  sa.sa_handler = fatalsig;
1094  for(ii = 0; ii < ALEN(sigs); ii++)
1095    sigaction(sigs[ii], &sa, NULL);
1096}
1097
1098static void
1099fatalsig(int sig) {
1100  struct sigaction sa;
1101
1102  if(SIGCOUNT_MAX <= ++global_sigcount) {
1103    bzero(&sa, sizeof(sa));
1104    sa.sa_handler = SIG_DFL;
1105    sigaction(sig, &sa, NULL);
1106    raise(sig);
1107  }
1108}
Note: See TracBrowser for help on using the repository browser.