source: trunk/gtk/main.c @ 2149

Last change on this file since 2149 was 2149, checked in by livings124, 14 years ago

Merge file selection and torrent creation into the main branch.

The new code for these features is under a new license.

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