source: trunk/gtk/main.c @ 1475

Last change on this file since 1475 was 1475, checked in by joshe, 16 years ago

Split off the window-related code into a new file.
Bump a couple copyright dates I forgot earlier.

  • Property svn:keywords set to Date Rev Author Id
File size: 23.4 KB
Line 
1/******************************************************************************
2 * $Id: main.c 1475 2007-02-07 07:35:33Z joshe $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/param.h>
26#include <errno.h>
27#include <signal.h>
28#include <string.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <time.h>
32#include <unistd.h>
33
34#include <gtk/gtk.h>
35#include <glib/gi18n.h>
36#include <glib/gstdio.h>
37
38#include "conf.h"
39#include "dialogs.h"
40#include "ipc.h"
41#include "msgwin.h"
42#include "tr_backend.h"
43#include "tr_torrent.h"
44#include "tr_cell_renderer_progress.h"
45#include "tr_window.h"
46#include "transmission.h"
47#include "util.h"
48
49#include "img_icon_full.h"
50
51/* time in seconds to wait for torrents to stop when exiting */
52#define TRACKER_EXIT_TIMEOUT    10
53
54/* interval in milliseconds to update the torrent list display */
55#define UPDATE_INTERVAL         1000
56
57/* interval in milliseconds to check for stopped torrents and update display */
58#define EXIT_CHECK_INTERVAL     500
59
60struct cbdata {
61  TrBackend *back;
62  GtkWindow *wind;
63  GtkTreeModel *model;
64  guint timer;
65  gboolean prefsopen;
66  gboolean msgwinopen;
67  gboolean closing;
68};
69
70struct exitdata {
71  struct cbdata *cbdata;
72  time_t started;
73  guint timer;
74};
75
76enum action
77{
78    ACT_OPEN = 0,
79    ACT_START,
80    ACT_STOP,
81    ACT_DELETE,
82    ACT_INFO,
83    ACT_PREF,
84    ACT_DEBUG,
85    ACTION_COUNT,
86};
87
88struct
89{
90    const char * label;
91    const char * icon;
92    int          flags;
93    const char * tooltip;
94}
95actions[] =
96{
97    { N_("Add"),         GTK_STOCK_ADD,         ACTF_WHEREVER | ACTF_ALWAYS,
98      N_("Add a new torrent") },
99    { N_("Start"),       GTK_STOCK_EXECUTE,     ACTF_WHEREVER | ACTF_INACTIVE,
100      N_("Start a torrent that is not running") },
101    { N_("Stop"),        GTK_STOCK_STOP,        ACTF_WHEREVER | ACTF_ACTIVE,
102      N_("Stop a torrent that is running") },
103    { N_("Remove"),      GTK_STOCK_REMOVE,      ACTF_WHEREVER | ACTF_WHATEVER,
104      N_("Remove a torrent") },
105    { N_("Properties"),  GTK_STOCK_PROPERTIES,  ACTF_WHEREVER | ACTF_WHATEVER,
106      N_("Show additional information about a torrent") },
107    { N_("Preferences"), GTK_STOCK_PREFERENCES, ACTF_WHEREVER | ACTF_ALWAYS,
108      N_("Customize application behavior") },
109    { N_("Open debug window"), NULL,            ACTF_MENU     | ACTF_ALWAYS,
110      NULL },
111};
112
113#define CBDATA_PTR              "callback-data-pointer"
114
115#define SIGCOUNT_MAX            3
116
117static sig_atomic_t global_sigcount = 0;
118
119static GList *
120readargs(int argc, char **argv);
121
122static void
123makewind(TrWindow *wind, TrBackend *back, benc_val_t *state, GList *args);
124static void
125quittransmission(struct cbdata *data);
126static gboolean
127winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
128static gboolean
129exitcheck(gpointer gdata);
130static void
131setupdrag(GtkWidget *widget, struct cbdata *data);
132static void
133gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
134        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
135
136static gboolean
137updatemodel(gpointer gdata);
138static void
139boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata);
140static void
141windact(GtkWidget *widget, int action, gpointer gdata);
142static void
143handleaction(struct cbdata *data, enum action action);
144
145static void
146addtorrents(void *vdata, void *state, GList *files,
147            const char *dir, guint flags);
148static void
149savetorrents(struct cbdata *data);
150static void
151safepipe(void);
152static void
153setupsighandlers(void);
154static void
155fatalsig(int sig);
156
157int
158main(int argc, char **argv) {
159  GtkWidget *mainwind, *preferr, *stateerr;
160  char *err;
161  TrBackend *back;
162  benc_val_t *state;
163  GList *argfiles;
164  gboolean didinit, didlock;
165  GdkPixbuf * icon;
166
167  safepipe();
168
169  argfiles = readargs(argc, argv);
170
171  didinit = cf_init(tr_getPrefsDirectory(), NULL);
172  didlock = FALSE;
173  if(NULL != argfiles && didinit && !(didlock = cf_lock(NULL)))
174    return !ipc_sendfiles_blocking(argfiles);
175
176  setupsighandlers();
177
178  gtk_init(&argc, &argv);
179
180  bindtextdomain("transmission-gtk", LOCALEDIR);
181  bind_textdomain_codeset("transmission-gtk", "UTF-8");
182  textdomain("transmission-gtk");
183
184  g_set_application_name(_("Transmission"));
185#if 0
186  /* this isn't used in transmission-gtk itself, it's for the .desktop file */
187  N_("BitTorrent Client");
188  /* this too */
189  N_("A free, lightweight client with a simple, intuitive interface");
190#endif
191
192  gtk_rc_parse_string(
193    "style \"transmission-standard\" {\n"
194    " GtkDialog::action-area-border = 6\n"
195    " GtkDialog::button-spacing = 12\n"
196    " GtkDialog::content-area-border = 6\n"
197    "}\n"
198    "widget \"TransmissionDialog\" style \"transmission-standard\"\n");
199
200  icon = gdk_pixbuf_new_from_inline( -1, tr_icon_full, FALSE, NULL );
201  gtk_window_set_default_icon( icon );
202  g_object_unref( icon );
203
204  if(didinit || cf_init(tr_getPrefsDirectory(), &err)) {
205    if(didlock || cf_lock(&err)) {
206
207      /* create main window now so any error dialogs can be it's children */
208      mainwind = tr_window_new();
209      preferr = NULL;
210      stateerr = NULL;
211
212      cf_loadprefs(&err);
213      if(NULL != err) {
214        preferr = errmsg(GTK_WINDOW(mainwind), "%s", err);
215        g_free(err);
216      }
217      state = cf_loadstate(&err);
218      if(NULL != err) {
219        stateerr = errmsg(GTK_WINDOW(mainwind), "%s", err);
220        g_free(err);
221      }
222
223      /* set libT message level */
224      msgwin_loadpref();
225
226      back = tr_backend_new();
227
228      /* apply a few prefs */
229      applyprefs(back);
230
231      makewind( TR_WINDOW( mainwind ), back, state, argfiles );
232
233      if(NULL != state)
234        cf_freestate(state);
235      g_object_unref(back);
236
237      if(NULL != preferr)
238        gtk_widget_show_all(preferr);
239      if(NULL != stateerr)
240        gtk_widget_show_all(stateerr);
241    } else {
242      gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit,
243                                  NULL, "%s", err));
244      g_free(err);
245    }
246  } else {
247    gtk_widget_show(errmsg_full(NULL, (callbackfunc_t)gtk_main_quit,
248                                NULL, "%s", err));
249    g_free(err);
250  }
251
252  if(NULL != argfiles)
253    freestrlist(argfiles);
254
255  gtk_main();
256
257  return 0;
258}
259
260GList *
261readargs(int argc, char **argv) {
262  char *name;
263
264  if(NULL == (name = strrchr(argv[0], '/')) || '\0' == *(++name))
265    name = argv[0];
266
267  while(0 < --argc) {
268    argv++;
269    if(0 == strcmp("--", *argv))
270      return checkfilenames(argc - 1, argv + 1);
271    else if('-' != argv[0][0])
272      return checkfilenames(argc, argv);
273    else if(0 == strcmp("-v", *argv) || 0 == strcmp("--version", *argv)) {
274      printf("%s %s (%d) http://transmission.m0k.org/\n",
275             name, VERSION_STRING, VERSION_REVISION);
276      exit(0);
277    }
278    else if(0 == strcmp("-h", *argv) || 0 == strcmp("--help", *argv)) {
279      printf("usage: %1$s [-hv] [files...]\n\n"
280"If %1$s is already running then a second copy will not be\n"
281"started, any torrents on the command-line will be opened in the first.\n",
282             name);
283      exit(0);
284    }
285  }
286
287  return NULL;
288}
289
290static void
291makewind( TrWindow * wind, TrBackend * back, benc_val_t * state, GList * args)
292{
293  GType types[] = {
294    /* info->name, info->totalSize, status,     error,      errorString, */
295    G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
296    /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
297    G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
298    /* peersUploading, peersDownloading, downloaded,    uploaded */
299    G_TYPE_INT,        G_TYPE_INT,       G_TYPE_UINT64, G_TYPE_UINT64,
300    /* the torrent object */
301    TR_TORRENT_TYPE};
302  struct cbdata *data = g_new0(struct cbdata, 1);
303  GtkListStore *store;
304  unsigned int ii;
305  GtkWidget *drag;
306
307  g_assert(MC_ROW_COUNT == ALEN(types));
308  store = gtk_list_store_newv(MC_ROW_COUNT, types);
309
310  g_object_ref(G_OBJECT(back));
311  data->back = back;
312  data->wind = GTK_WINDOW(wind);
313  data->timer = 0;
314  data->model = GTK_TREE_MODEL(store);
315  data->prefsopen = FALSE;
316  data->msgwinopen = FALSE;
317  data->closing = FALSE;
318
319  g_assert( ACTION_COUNT == ALEN( actions ) );
320  for( ii = 0; ii < ALEN( actions ); ii++ )
321  {
322      tr_window_action_add( wind, ii, actions[ii].flags,
323                            gettext( actions[ii].label ),
324                            actions[ii].icon,
325                            gettext( actions[ii].tooltip ) );
326  }
327  g_object_set( wind, "model", data->model,
328                      "double-click-action", ACT_INFO, NULL);
329
330  g_signal_connect( wind, "action",       G_CALLBACK( windact  ), data );
331  g_signal_connect( wind, "delete_event", G_CALLBACK( winclose ), data );
332
333  g_object_get( wind, "drag-widget", &drag, NULL );
334  setupdrag( drag, data );
335
336  addtorrents(data, state, args, NULL, addactionflag(cf_getpref(PREF_ADDIPC)));
337
338  data->timer = g_timeout_add(UPDATE_INTERVAL, updatemodel, data);
339  updatemodel(data);
340
341  /* this shows the window */
342  tr_window_size_hack( wind );
343
344  /* set up the ipc socket now that we're ready to get torrents from it */
345  ipc_socket_setup(GTK_WINDOW(wind), addtorrents, data);
346}
347
348static void
349quittransmission( struct cbdata * data )
350{
351    g_object_unref( G_OBJECT( data->back ) );
352    if( NULL != data->wind )
353    {
354        gtk_widget_destroy( GTK_WIDGET( data->wind ) );
355    }
356    g_object_unref( data->model );
357    if( 0 < data->timer )
358    {
359        g_source_remove( data->timer );
360    }
361    g_free( data );
362    gtk_main_quit();
363}
364
365gboolean
366winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
367  struct cbdata *data = gdata;
368  struct exitdata *edata;
369  GtkTreeIter iter;
370  TrTorrent *tor;
371
372  data->closing = TRUE;
373
374  /* stop the update timer */
375  if(0 < data->timer)
376    g_source_remove(data->timer);
377  data->timer = 0;
378
379  /*
380    Add a reference to all torrents in the list, which will be removed
381    when the politely-stopped signal is emitted.  This is necessary
382    because a reference is added when a torrent is removed
383    from the model and tr_torrent_stop_polite() is called on it.
384  */
385  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
386    do
387      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
388    while(gtk_tree_model_iter_next(data->model, &iter));
389  }
390
391  /* try to politely stop all the torrents */
392  tr_backend_stop_torrents(data->back);
393
394  /* shut down nat traversal */
395  tr_natTraversalEnable(tr_backend_handle(data->back), 0);
396
397  /* set things up to wait for torrents to stop */
398  edata = g_new0(struct exitdata, 1);
399  edata->cbdata = data;
400  edata->started = time(NULL);
401  /* check if torrents are still running */
402  if(exitcheck(edata)) {
403    /* yes, start the exit timer and disable widgets */
404    edata->timer = g_timeout_add(EXIT_CHECK_INTERVAL, exitcheck, edata);
405    gtk_widget_set_sensitive( GTK_WIDGET( data->wind ), FALSE );
406  }
407
408  /* returning FALSE means to destroy the window */
409  return TRUE;
410}
411
412gboolean
413exitcheck(gpointer gdata) {
414  struct exitdata *data = gdata;
415  tr_handle_status_t * hstat;
416
417  hstat = tr_handleStatus( tr_backend_handle( data->cbdata->back ) );
418
419  /* keep going if we haven't hit the exit timeout and
420     we either have torrents left or nat traversal is stopping */
421  if( time( NULL ) - data->started < TRACKER_EXIT_TIMEOUT &&
422      ( !tr_backend_torrents_stopped( data->cbdata->back ) ||
423        TR_NAT_TRAVERSAL_DISABLED != hstat->natTraversalStatus ) ) {
424    updatemodel(data->cbdata);
425    return TRUE;
426  }
427
428  /* exit otherwise */
429  if(0 < data->timer)
430    g_source_remove(data->timer);
431  quittransmission(data->cbdata);
432  g_free(data);
433
434  return FALSE;
435}
436
437static void
438gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
439        gint y SHUTUP, GtkSelectionData *sel, guint info SHUTUP, guint time,
440        gpointer gdata) {
441  struct cbdata *data = gdata;
442  char prefix[] = "file:";
443  char *files, *decoded, *deslashed, *hostless;
444  int ii, len;
445  GList *errs;
446  struct stat sb;
447  int prelen = strlen(prefix);
448  GList *paths, *freeables;
449
450#ifdef DND_DEBUG
451  char *sele = gdk_atom_name(sel->selection);
452  char *targ = gdk_atom_name(sel->target);
453  char *type = gdk_atom_name(sel->type);
454
455  fprintf(stderr, "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i\n",
456          sele, targ, type, sel->format, sel->length);
457  g_free(sele);
458  g_free(targ);
459  g_free(type);
460  if(8 == sel->format) {
461    for(ii = 0; ii < sel->length; ii++)
462      fprintf(stderr, "%02X ", sel->data[ii]);
463    fprintf(stderr, "\n");
464  }
465#endif
466
467  errs = NULL;
468  paths = NULL;
469  freeables = NULL;
470  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
471     8 == sel->format) {
472    /* split file list on carriage returns and linefeeds */
473    files = g_new(char, sel->length + 1);
474    memcpy(files, sel->data, sel->length);
475    files[sel->length] = '\0';
476    for(ii = 0; '\0' != files[ii]; ii++)
477      if('\015' == files[ii] || '\012' == files[ii])
478        files[ii] = '\0';
479
480    /* try to get a usable filename out of the URI supplied and add it */
481    for(ii = 0; ii < sel->length; ii += len + 1) {
482      if('\0' == files[ii])
483        len = 0;
484      else {
485        len = strlen(files + ii);
486        /* de-urlencode the URI */
487        decoded = urldecode(files + ii, len);
488        freeables = g_list_append(freeables, decoded);
489        if(g_utf8_validate(decoded, -1, NULL)) {
490          /* remove the file: prefix */
491          if(prelen < len && 0 == strncmp(prefix, decoded, prelen)) {
492            deslashed = decoded + prelen;
493            /* trim excess / characters from the beginning */
494            while('/' == deslashed[0] && '/' == deslashed[1])
495              deslashed++;
496            /* if the file doesn't exist, the first part might be a hostname */
497            if(0 > g_stat(deslashed, &sb) &&
498               NULL != (hostless = strchr(deslashed + 1, '/')) &&
499               0 == g_stat(hostless, &sb))
500              deslashed = hostless;
501            /* finally, add it to the list of torrents to try adding */
502            paths = g_list_append(paths, deslashed);
503          }
504        }
505      }
506    }
507
508    /* try to add any torrents we found */
509    if(NULL != paths)
510      addtorrents(data, NULL, paths, NULL,
511                  addactionflag(cf_getpref(PREF_ADDSTD)));
512    freestrlist(freeables);
513    g_free(files);
514  }
515
516  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
517}
518
519static void
520setupdrag(GtkWidget *widget, struct cbdata *data) {
521  GtkTargetEntry targets[] = {
522    { "STRING",     0, 0 },
523    { "text/plain", 0, 0 },
524    { "text/uri-list", 0, 0 },
525  };
526
527  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
528
529  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
530                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
531}
532
533gboolean
534updatemodel(gpointer gdata) {
535  struct cbdata *data = gdata;
536  TrTorrent *tor;
537  tr_stat_t *st;
538  tr_info_t *in;
539  GtkTreeIter iter;
540  float up, down;
541
542  if(0 < global_sigcount) {
543    quittransmission(data);
544    return FALSE;
545  }
546
547  if(gtk_tree_model_get_iter_first(data->model, &iter)) {
548    do {
549      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
550      st = tr_torrent_stat(tor);
551      in = tr_torrent_info(tor);
552      g_object_unref(tor);
553      /* XXX find out if setting the same data emits changed signal */
554      gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
555        MC_SIZE, in->totalSize, MC_STAT, st->status, MC_ERR, st->error,
556        MC_TERR, st->errorString, MC_PROG, st->progress,
557        MC_DRATE, st->rateDownload, MC_URATE, st->rateUpload, MC_ETA, st->eta,
558        MC_PEERS, st->peersTotal, MC_UPEERS, st->peersUploading,
559        MC_DPEERS, st->peersDownloading, MC_DOWN, st->downloaded,
560        MC_UP, st->uploaded, -1);
561    } while(gtk_tree_model_iter_next(data->model, &iter));
562  }
563
564  /* update the main window's statusbar and toolbar buttons */
565  tr_torrentRates( tr_backend_handle( data->back ), &down, &up );
566  tr_window_update( TR_WINDOW(data->wind), down, up );
567
568  /* check for politely stopped torrents unless we're exiting */
569  if(!data->closing)
570    tr_backend_torrents_stopped(data->back);
571
572  /* update the message window */
573  msgwin_update();
574
575  return TRUE;
576}
577
578static void
579boolwindclosed(GtkWidget *widget SHUTUP, gpointer gdata) {
580  gboolean *preachy_gcc = gdata;
581 
582  *preachy_gcc = FALSE;
583}
584
585static void
586windact( GtkWidget * wind SHUTUP, int action, gpointer gdata )
587{
588    g_assert( 0 <= action );
589    handleaction( gdata, action );
590}
591
592static void
593handleaction( struct cbdata * data, enum action act )
594{
595  GtkTreeSelection *sel;
596  GList *rows, *ii;
597  GtkTreeRowReference *ref;
598  GtkTreePath *path;
599  GtkTreeIter iter;
600  TrTorrent *tor;
601  int status;
602  gboolean changed;
603  GtkWidget * win;
604
605  g_assert( ACTION_COUNT > act );
606
607  switch( act )
608  {
609      case ACT_OPEN:
610          makeaddwind( data->wind, addtorrents, data );
611          return;
612      case ACT_PREF:
613          if( !data->prefsopen )
614          {
615              data->prefsopen = TRUE;
616              win = makeprefwindow( data->wind, data->back );
617              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
618                                &data->prefsopen );
619          }
620          return;
621      case ACT_DEBUG:
622          if( !data->msgwinopen )
623          {
624              data->msgwinopen = TRUE;
625              win = msgwin_create();
626              g_signal_connect( win, "destroy", G_CALLBACK( boolwindclosed ),
627                                &data->msgwinopen );
628          }
629          return;
630      case ACT_START:
631      case ACT_STOP:
632      case ACT_DELETE:
633      case ACT_INFO:
634      case ACTION_COUNT:
635          break;
636  }
637
638  /* get a list of references to selected rows */
639  g_object_get( data->wind, "selection", &sel, NULL );
640  rows = gtk_tree_selection_get_selected_rows( sel, NULL );
641  for(ii = rows; NULL != ii; ii = ii->next) {
642    ref = gtk_tree_row_reference_new(data->model, ii->data);
643    gtk_tree_path_free(ii->data);
644    ii->data = ref;
645  }
646
647  changed = FALSE;
648  for(ii = rows; NULL != ii; ii = ii->next) {
649    if(NULL != (path = gtk_tree_row_reference_get_path(ii->data)) &&
650       gtk_tree_model_get_iter(data->model, &iter, path)) {
651      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
652                         MC_STAT, &status, -1);
653      if( ACT_ISAVAIL( actions[act].flags, status ) )
654
655      {
656          switch( act )
657          {
658              case ACT_START:
659                  tr_torrentStart( tr_torrent_handle( tor ) );
660                  changed = TRUE;
661                  break;
662              case ACT_STOP:
663                  tr_torrentStop( tr_torrent_handle( tor ) );
664                  changed = TRUE;
665                  break;
666              case ACT_DELETE:
667                  /* tor will be unref'd in the politely_stopped handler */
668                  g_object_ref( tor );
669                  tr_torrent_stop_politely( tor );
670                  if( TR_FLAG_SAVE & tr_torrent_info( tor )->flags )
671                  {
672                      tr_torrentRemoveSaved( tr_torrent_handle( tor ) );
673                  }
674                  gtk_list_store_remove( GTK_LIST_STORE( data->model ),
675                                         &iter );
676                  changed = TRUE;
677                  break;
678              case ACT_INFO:
679                  makeinfowind( data->wind, tor );
680                  break;
681              case ACT_OPEN:
682              case ACT_PREF:
683              case ACT_DEBUG:
684              case ACTION_COUNT:
685                  break;
686          }
687      }
688      g_object_unref(tor);
689    }
690    if(NULL != path)
691      gtk_tree_path_free(path);
692    gtk_tree_row_reference_free(ii->data);
693  }
694  g_list_free(rows);
695
696  if(changed) {
697    savetorrents(data);
698    updatemodel(data);
699  }
700}
701
702static const char *
703defaultdir( void )
704{
705    static char * wd = NULL;
706    const char  * dir;
707
708    dir = cf_getpref( PREF_DIR );
709    if( NULL == dir )
710    {
711        if( NULL == wd )
712        {
713            wd = g_new( char, MAX_PATH_LENGTH + 1 );
714            if( NULL == getcwd( wd, MAX_PATH_LENGTH + 1 ) )
715            {
716                strcpy( wd, "." );
717            }
718        }
719        dir = wd;
720    }
721
722    return dir;
723}
724
725static void
726addtorrents(void *vdata, void *state, GList *files,
727            const char *dir, guint flags) {
728  struct cbdata *data = vdata;
729  GList *torlist, *errlist, *ii;
730  char *errstr;
731  TrTorrent *tor;
732  GtkTreeIter iter;
733  const char * pref;
734
735  errlist = NULL;
736  torlist = NULL;
737
738  if(NULL != state)
739    torlist = tr_backend_load_state(data->back, state, &errlist);
740
741  if(NULL != files) {
742    if( NULL == dir )
743    {
744        pref = cf_getpref( PREF_ASKDIR );
745        if( NULL != pref && strbool( pref ) )
746        {
747            promptfordir( data->wind, addtorrents, data,
748                          files, flags, defaultdir() );
749            files = NULL;
750        }
751        dir = defaultdir();
752    }
753    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
754      errstr = NULL;
755      tor = tr_torrent_new(G_OBJECT(data->back), ii->data, dir,
756                           flags, &errstr);
757      if(NULL != tor)
758        torlist = g_list_append(torlist, tor);
759      if(NULL != errstr)
760        errlist = g_list_append(errlist, errstr);
761    }
762  }
763
764  for(ii = g_list_first(torlist); NULL != ii; ii = ii->next) {
765    gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
766    gtk_list_store_set(GTK_LIST_STORE(data->model), &iter,
767                       MC_TORRENT, ii->data, -1);
768    /* we will always ref a torrent before politely stopping it */
769    g_signal_connect(ii->data, "politely_stopped",
770                     G_CALLBACK(g_object_unref), data);
771    g_object_unref(ii->data);
772  }
773
774  if(NULL != errlist) {
775    errstr = joinstrlist(errlist, "\n");
776    errmsg(data->wind, ngettext("Failed to load torrent file:\n%s",
777                                "Failed to load torrent files:\n%s",
778                                g_list_length(errlist)), errstr);
779    g_list_foreach(errlist, (GFunc)g_free, NULL);
780    g_list_free(errlist);
781    g_free(errstr);
782  }
783
784  if(NULL != torlist) {
785    updatemodel(data);
786    savetorrents(data);
787  }
788}
789
790static void
791savetorrents( struct cbdata *data )
792{
793    char * errstr;
794
795    tr_backend_save_state( data->back, &errstr );
796    if( NULL != errstr )
797    {
798        errmsg( data->wind, "%s", errstr );
799        g_free( errstr );
800    }
801}
802
803static void
804safepipe(void) {
805  struct sigaction sa;
806
807  bzero(&sa, sizeof(sa));
808  sa.sa_handler = SIG_IGN;
809  sigaction(SIGPIPE, &sa, NULL);
810}
811
812static void
813setupsighandlers(void) {
814  int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
815  struct sigaction sa;
816  unsigned int ii;
817
818  bzero(&sa, sizeof(sa));
819  sa.sa_handler = fatalsig;
820  for(ii = 0; ii < ALEN(sigs); ii++)
821    sigaction(sigs[ii], &sa, NULL);
822}
823
824static void
825fatalsig(int sig) {
826  struct sigaction sa;
827
828  if(SIGCOUNT_MAX <= ++global_sigcount) {
829    bzero(&sa, sizeof(sa));
830    sa.sa_handler = SIG_DFL;
831    sigaction(sig, &sa, NULL);
832    raise(sig);
833  }
834}
Note: See TracBrowser for help on using the repository browser.