source: branches/file_selection/gtk/main.c @ 2131

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