Changeset 248


Ignore:
Timestamp:
May 3, 2006, 6:58:16 AM (15 years ago)
Author:
joshe
Message:

Major internal restructuring for the GTK GUI,

GObject-derived wrappers are used for tr_handle_t and tr_torrent_t.

Use bencoding to store prefs and state file.
Make sure to always group error messages when adding multiple torrents at once.
Remove some unused code.
Many miscellaneous cleanups.

Location:
trunk/gtk
Files:
6 added
2 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/gtk/Makefile

    r243 r248  
    22include ../Makefile.common
    33
    4 SRCS = conf.c dialogs.c io.c ipc.c main.c trcellrenderertorrent.c util.c
     4SRCS = conf.c dialogs.c io.c ipc.c main.c tr_backend.c tr_torrent.c \
     5        tr_cell_renderer_torrent.c util.c
    56OBJS = $(SRCS:%.c=%.o)
    67
  • trunk/gtk/conf.c

    r242 r248  
    4141
    4242#include "conf.h"
    43 #include "transmission.h"
    4443#include "util.h"
    4544
     
    6261static void
    6362cf_removelocks(void);
     63static char *
     64cf_readfile(const char *file, const char *oldfile, gsize *len,
     65            gboolean *usedold, char **errstr);
     66static void
     67cf_benc_append(benc_val_t *val, char type, int incsize);
     68static void
     69cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
     70             char **errstr);
    6471static gboolean
    6572writefile_traverse(gpointer key, gpointer value, gpointer data);
    6673static char *
    67 getstateval(struct cf_torrentstate *state, char *line);
     74getstateval(benc_val_t *state, char *line);
    6875
    6976static char *gl_confdir = NULL;
     
    172179}
    173180
    174 gboolean
    175 cf_loadprefs(char **errstr) {
    176   char *path = g_build_filename(gl_confdir, FILE_PREFS, NULL);
    177   char *oldpath;
     181static char *
     182cf_readfile(const char *file, const char *oldfile, gsize *len,
     183            gboolean *usedold, char **errstr) {
     184  char *path;
    178185  GIOChannel *io;
    179186  GError *err;
    180   char *line, *sep;
    181   gsize len, termpos;
    182   char term = PREF_SEP_LINE;
     187  char *ret;
    183188
    184189  *errstr = NULL;
    185 
    186   if(NULL != gl_prefs)
    187     g_tree_destroy(gl_prefs);
    188 
    189   gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
    190                           g_free, g_free);
    191 
     190  *usedold = FALSE;
     191  ret = NULL;
    192192  err = NULL;
     193  *len = 0;
     194
     195  path = g_build_filename(gl_confdir, file, NULL);
    193196  io = g_io_channel_new_file(path, "r", &err);
     197  if(NULL != err && g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
     198    g_error_free(err);
     199    err = NULL;
     200    g_free(path);
     201    path = g_build_filename(gl_old_confdir, oldfile, NULL);
     202    io = g_io_channel_new_file(path, "r", &err);
     203    *usedold = TRUE;
     204  }
    194205  if(NULL != err) {
    195206    if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
    196207      *errstr = g_strdup_printf(
    197208        _("Failed to open the file %s for reading:\n%s"), path, err->message);
    198     else {
    199       g_error_free(err);
    200       err = NULL;
    201       oldpath = g_build_filename(gl_old_confdir, OLD_FILE_PREFS, NULL);
    202       io = g_io_channel_new_file(oldpath, "r", &err);
    203       g_free(oldpath);
    204     }
    205     if(NULL != err)
    206       goto done;
    207   }
    208   g_io_channel_set_line_term(io, &term, 1);
    209 
    210   err = NULL;
    211   for(;;) {
    212     assert(NULL == err) ;
    213     switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
    214       case G_IO_STATUS_ERROR:
    215         *errstr = g_strdup_printf(
    216           _("Error while reading from the file %s:\n%s"), path, err->message);
    217         goto done;
    218       case G_IO_STATUS_NORMAL:
    219         if(NULL != line) {
    220           if(g_utf8_validate(line, len, NULL) &&
    221              NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
    222             *sep = '\0';
    223             line[termpos] = '\0';
    224             g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep + 1));
    225           }
    226           g_free(line);
    227         }
    228         break;
    229       case G_IO_STATUS_EOF:
    230         goto done;
    231       default:
    232         assert(!"unknown return code");
    233         goto done;
    234     }
     209    goto done;
     210  }
     211  g_io_channel_set_encoding(io, NULL, NULL);
     212
     213  if(G_IO_STATUS_ERROR == g_io_channel_read_to_end(io, &ret, len, &err)) {
     214    *errstr = g_strdup_printf(
     215      _("Error while reading from the file %s:\n%s"), path, err->message);
     216    goto done;
    235217  }
    236218
     
    240222  if(NULL != io) 
    241223    g_io_channel_unref(io);
    242   g_free(path);
    243   return NULL == *errstr;
     224  return ret;
     225}
     226
     227void
     228cf_loadprefs(char **errstr) {
     229  char *data, *line, *eol, *sep, *key;
     230  gsize len;
     231  benc_val_t val;
     232  gboolean usedold;
     233  int ii;
     234
     235  *errstr = NULL;
     236
     237  if(NULL != gl_prefs)
     238    g_tree_destroy(gl_prefs);
     239
     240  gl_prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
     241                          g_free, g_free);
     242
     243  data = cf_readfile(FILE_PREFS, OLD_FILE_PREFS, &len, &usedold, errstr);
     244  if(NULL != *errstr) {
     245    g_assert(NULL == data);
     246    return;
     247  }
     248
     249  if(NULL == data)
     250    return;
     251
     252  bzero(&val, sizeof(val));
     253  if(!usedold && !tr_bencLoad(data, len, &val, NULL)) {
     254    if(TYPE_DICT == val.type) {
     255      key = NULL;
     256      for(ii = 0; ii < val.val.l.count; ii++) {
     257        if(NULL == key) {
     258          g_assert(TYPE_STR == val.val.l.vals[ii].type);
     259          key = val.val.l.vals[ii].val.s.s;
     260        } else {
     261          if(TYPE_INT == val.val.l.vals[ii].type)
     262            g_tree_insert(gl_prefs, g_strdup(key),
     263                         g_strdup_printf("%"PRIu64, val.val.l.vals[ii].val.i));
     264          else if(TYPE_STR == val.val.l.vals[ii].type)
     265            g_tree_insert(gl_prefs, g_strdup(key),
     266                          g_strdup(val.val.l.vals[ii].val.s.s));
     267          key = NULL;
     268        }
     269      }
     270    }
     271
     272  } else {
     273    /* XXX remove this in a release or two */
     274    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
     275        line = eol + 1) {
     276      *eol = '\0';
     277      if(g_utf8_validate(line, -1, NULL) &&
     278         NULL != (sep = strchr(line, PREF_SEP_KEYVAL))) {
     279        *sep = '\0';
     280        g_tree_insert(gl_prefs, g_strcompress(line), g_strcompress(sep+1));
     281      }
     282    }
     283    cf_saveprefs(errstr);
     284  }
     285
     286  g_free(data);
     287}
     288
     289benc_val_t *
     290cf_loadstate(char **errstr) {
     291  char *data, *line, *eol, *prog;
     292  gsize len;
     293  gboolean usedold;
     294  benc_val_t *state, *torstate;
     295
     296  *errstr = NULL;
     297
     298  data = cf_readfile(FILE_STATE, OLD_FILE_STATE, &len, &usedold, errstr);
     299  if(NULL != *errstr) {
     300    g_assert(NULL == data);
     301    return NULL;
     302  }
     303
     304  if(NULL == data)
     305    return NULL;
     306
     307  state = g_new0(benc_val_t, 1);
     308  if(usedold || tr_bencLoad(data, len, state, NULL)) {
     309    /* XXX all this evil compat code should go away at some point */
     310    tr_bencFree(state);
     311    bzero(state, sizeof(benc_val_t));
     312    state->type = TYPE_LIST;
     313    for(line = data; NULL != (eol = strchr(line, PREF_SEP_LINE));
     314        line = eol + 1) {
     315      *eol = '\0';
     316      if(g_utf8_validate(line, -1, NULL)) {
     317        cf_benc_append(state, TYPE_DICT, 10);
     318        torstate = state->val.l.vals + state->val.l.count - 1;
     319        prog = line;
     320        while(NULL != (prog = getstateval(torstate, prog)))
     321          ;
     322      }
     323    }
     324  }
     325
     326  g_free(data);
     327
     328  return state;
     329}
     330
     331static void
     332cf_benc_append(benc_val_t *val, char type, int incsize) {
     333  if(++val->val.l.count > val->val.l.alloc) {
     334    val->val.l.alloc += incsize;
     335    val->val.l.vals = g_renew(benc_val_t, val->val.l.vals, val->val.l.alloc);
     336    bzero(val->val.l.vals + val->val.l.alloc - incsize,
     337          incsize * sizeof(benc_val_t));
     338  }
     339  val->val.l.vals[val->val.l.count-1].type = type;
    244340}
    245341
     
    258354}
    259355
    260 struct writeinfo {
    261   GIOChannel *io;
     356static void
     357cf_writebenc(const char *file, const char *tmp, benc_val_t *data,
     358             char **errstr) {
     359  char *path = g_build_filename(gl_confdir, file, NULL);
     360  char *pathtmp = g_build_filename(gl_confdir, tmp, NULL);
     361  GIOChannel *io = NULL;
    262362  GError *err;
    263 };
    264 
    265 gboolean
    266 cf_saveprefs(char **errstr) {
    267   char *file = g_build_filename(gl_confdir, FILE_PREFS, NULL);
    268   char *tmpfile = g_build_filename(gl_confdir, FILE_PREFS_TMP, NULL);
    269   GIOChannel *io = NULL;
    270   struct writeinfo info;
    271   int fd;
    272 
    273   assert(NULL != gl_prefs);
    274   assert(NULL != errstr);
     363  char *datastr;
     364  size_t len;
     365  gsize written;
    275366
    276367  *errstr = NULL;
    277 
    278   if(0 > (fd = lockfile(tmpfile, errstr))) {
    279     g_free(errstr);
    280     *errstr = g_strdup_printf(_("Failed to open or lock the file %s:\n%s"),
    281                               tmpfile, strerror(errno));
     368  err = NULL;
     369  datastr = NULL;
     370
     371  io = g_io_channel_new_file(pathtmp, "w", &err);
     372  if(NULL != err) {
     373    *errstr = g_strdup_printf(_("Failed to open the file %s for writing:\n%s"),
     374                              pathtmp, err->message);
    282375    goto done;
    283376  }
    284 
    285 #ifdef NDEBUG
    286   ftruncate(fd, 0);
    287 #else
    288   assert(0 == ftruncate(fd, 0));
    289 #endif
    290 
    291   info.err = NULL;
    292   io = g_io_channel_unix_new(fd);
    293   g_io_channel_set_close_on_unref(io, TRUE);
    294 
    295   info.io = io;
    296   info.err = NULL;
    297   g_tree_foreach(gl_prefs, writefile_traverse, &info);
    298   if(NULL != info.err ||
    299      G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
     377  g_io_channel_set_encoding(io, NULL, NULL);
     378
     379  len = 0;
     380  datastr = tr_bencSaveMalloc(data, &len);
     381
     382  written = 0;
     383  g_io_channel_write_chars(io, datastr, len, &written, &err);
     384  if(NULL != err)
     385    g_io_channel_flush(io, &err);
     386  if(NULL != err) {
    300387    *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
    301                               tmpfile, info.err->message);
    302     g_error_free(info.err);
     388                              pathtmp, err->message);
    303389    goto done;
    304390  }
    305391
    306   if(0 > rename(tmpfile, file)) {
     392  if(0 > rename(pathtmp, path)) {
    307393    *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"),
    308                               tmpfile, file, strerror(errno));
     394                              pathtmp, file, strerror(errno));
    309395    goto done;
    310396  }
    311397
    312398 done:
    313   g_free(file);
    314   g_free(tmpfile);
     399  g_free(path);
     400  g_free(pathtmp);
    315401  if(NULL != io)
    316402    g_io_channel_unref(io);
    317 
    318   return NULL == *errstr;
     403  if(NULL != datastr)
     404    free(datastr);
     405}
     406
     407void
     408cf_saveprefs(char **errstr) {
     409  benc_val_t val;
     410  benc_val_t *ptr;
     411
     412  *errstr = NULL;
     413
     414  bzero(&val, sizeof(val));
     415  val.type = TYPE_DICT;
     416  val.val.l.alloc = val.val.l.count = g_tree_nnodes(gl_prefs) * 2;
     417  val.val.l.vals = g_new0(benc_val_t, val.val.l.alloc);
     418
     419  ptr = val.val.l.vals;
     420  g_tree_foreach(gl_prefs, writefile_traverse, &ptr);
     421  g_assert(ptr - val.val.l.vals == val.val.l.alloc);
     422
     423  cf_writebenc(FILE_PREFS, FILE_PREFS_TMP, &val, errstr);
     424  tr_bencFree(&val);
    319425}
    320426
    321427static gboolean
    322428writefile_traverse(gpointer key, gpointer value, gpointer data) {
    323   struct writeinfo *info = data;
    324   char *ekey, *eval, *line;
    325   char sep[2];
    326   int len;
    327 
    328   ekey = g_strescape(key, NULL);
    329   eval = g_strescape(value, NULL);
    330   sep[0] = PREF_SEP_KEYVAL;
    331   sep[1] = '\0';
    332   line = g_strjoin(sep, ekey, eval, NULL);
    333   len = strlen(line);
    334   line[len] = PREF_SEP_LINE;
    335 
    336   switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
    337     case G_IO_STATUS_ERROR:
    338       goto done;
    339     case G_IO_STATUS_NORMAL:
    340       break;
    341     default:
    342       assert(!"unknown return code");
    343       goto done;
    344   }
    345 
    346  done:
    347   g_free(ekey);
    348   g_free(eval);
    349   g_free(line);
    350   return NULL != info->err;
    351 }
    352 
    353 GList *
    354 cf_loadstate(char **errstr) {
    355   char *path = g_build_filename(gl_confdir, FILE_STATE, NULL);
    356   char *oldpath;
    357   GIOChannel *io;
    358   GError *err;
    359   char term = STATE_SEP;
    360   GList *ret = NULL;
    361   char *line, *ptr;
    362   gsize len, termpos;
    363   struct cf_torrentstate *ts;
    364 
    365   err = NULL;
    366   io = g_io_channel_new_file(path, "r", &err);
    367   if(NULL != err) {
    368     if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
    369       *errstr = g_strdup_printf(
    370         _("Failed to open the file %s for reading:\n%s"), path, err->message);
    371     else {
    372       g_error_free(err);
    373       err = NULL;
    374       oldpath = g_build_filename(gl_old_confdir, OLD_FILE_STATE, NULL);
    375       io = g_io_channel_new_file(oldpath, "r", &err);
    376       g_free(oldpath);
    377     }
    378     if(NULL != err)
    379       goto done;
    380   }
    381   g_io_channel_set_line_term(io, &term, 1);
    382 
    383   err = NULL;
    384   for(;;) {
    385     assert(NULL == err);
    386     switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
    387       case G_IO_STATUS_ERROR:
    388         *errstr = g_strdup_printf(
    389           _("Error while reading from the file %s:\n%s"), path, err->message);
    390         goto done;
    391       case G_IO_STATUS_NORMAL:
    392         if(NULL != line) {
    393           if(g_utf8_validate(line, -1, NULL)) {
    394             ts = g_new0(struct cf_torrentstate, 1);
    395             ptr = line;
    396             while(NULL != (ptr = getstateval(ts, ptr)))
    397               ;
    398             if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
    399               ret = g_list_append(ret, ts);
    400             else
    401               cf_freestate(ts);
    402           }
    403           g_free(line);
    404         }
    405         break;
    406       case G_IO_STATUS_EOF:
    407         goto done;
    408       default:
    409         assert(!"unknown return code");
    410         goto done;
    411     }
    412   }
    413 
    414  done:
    415   if(NULL != err)
    416     g_error_free(err);
    417   if(NULL != io) 
    418     g_io_channel_unref(io);
    419   if(NULL != *errstr && NULL != ret) {
    420     g_list_foreach(ret, (GFunc)g_free, NULL);
    421     g_list_free(ret);
    422     ret = NULL;
    423   }
    424   g_free(path);
    425   return ret;
     429  benc_val_t **ptr = data;
     430  benc_val_t *bkey = *ptr;
     431  benc_val_t *bval = (*ptr) + 1;
     432
     433  *ptr = (*ptr) + 2;
     434
     435  bkey->type = TYPE_STR;
     436  bkey->val.s.s = key;
     437  bkey->val.s.i = strlen(key);
     438
     439  bval->type = TYPE_STR;
     440  bval->val.s.s = value;
     441  bval->val.s.i = strlen(value);
     442
     443  return FALSE;
    426444}
    427445
    428446static char *
    429 getstateval(struct cf_torrentstate *state, char *line) {
     447getstateval(benc_val_t *state, char *line) {
    430448  char *start, *end;
    431449
     
    460478
    461479  /* if it's a key we recognize then save the data */
    462   if(0 == strcmp(line, "torrent"))
    463     state->ts_torrent = g_strcompress(start);
    464   else if(0 == strcmp(line, "dir"))
    465     state->ts_directory = g_strcompress(start);
    466   else if(0 == strcmp(line, "paused"))
    467     state->ts_paused = strbool(start);
     480  if(0 == strcmp(line, "torrent") || 0 == strcmp(line, "dir") ||
     481     0 == strcmp(line, "paused")) {
     482    cf_benc_append(state, TYPE_STR, 6);
     483    state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(line);
     484    state->val.l.vals[state->val.l.count-1].val.s.i = strlen(line);
     485    if('p' == *line) {
     486      cf_benc_append(state, TYPE_INT, 6);
     487      state->val.l.vals[state->val.l.count-1].val.i = strbool(start);
     488    } else {
     489      cf_benc_append(state, TYPE_STR, 6);
     490      state->val.l.vals[state->val.l.count-1].val.s.s = g_strdup(start);
     491      state->val.l.vals[state->val.l.count-1].val.s.i = strlen(start);
     492    }
     493  }
    468494
    469495  /* return a pointer to just past the end of the value */
     
    471497}
    472498
    473 gboolean
    474 cf_savestate(GList *torrents, char **errstr) {
    475   char *file = g_build_filename(gl_confdir, FILE_STATE, NULL);
    476   char *tmpfile = g_build_filename(gl_confdir, FILE_STATE_TMP, NULL);
    477   GIOChannel *io = NULL;
    478   GError *err;
    479   int fd;
    480   char *torrentfile, *torrentdir, *line;
    481   gsize written;
    482   gboolean paused;
    483   GIOStatus res;
    484   tr_stat_t *sb;
    485   tr_info_t *in;
    486 
     499void
     500cf_savestate(benc_val_t *state, char **errstr) {
    487501  *errstr = NULL;
    488 
    489   if(0 > (fd = lockfile(tmpfile, errstr))) {
    490     g_free(errstr);
    491     *errstr = g_strdup_printf(_("Failed to open or lock the file %s:\n%s"),
    492                               tmpfile, strerror(errno));
    493     goto done;
    494   }
    495 
    496 #ifdef NDEBUG
    497   ftruncate(fd, 0);
    498 #else
    499   assert(0 == ftruncate(fd, 0));
    500 #endif
    501 
    502   io = g_io_channel_unix_new(fd);
    503   g_io_channel_set_close_on_unref(io, TRUE);
    504 
    505   err = NULL;
    506   while(NULL != torrents) {
    507     sb = tr_torrentStat(torrents->data);
    508     in = tr_torrentInfo(torrents->data);
    509     paused = (TR_STATUS_INACTIVE & sb->status);
    510     torrentfile = g_strescape(in->torrent, "");
    511     torrentdir = g_strescape(tr_torrentGetFolder(torrents->data), "");
    512     /* g_strcompress */
    513     line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
    514                            torrentfile, torrentdir, (paused ? "yes" : "no"),
    515                            STATE_SEP);
    516     res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
    517     g_free(torrentfile);
    518     g_free(torrentdir);
    519     g_free(line);
    520     switch(res) {
    521       case G_IO_STATUS_ERROR:
    522         goto done;
    523       case G_IO_STATUS_NORMAL:
    524         break;
    525       default:
    526         assert(!"unknown return code");
    527         goto done;
    528     }
    529     torrents = torrents->next;
    530   }
    531   if(NULL != err ||
    532      G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
    533     *errstr = g_strdup_printf(_("Error while writing to the file %s:\n%s"),
    534                               tmpfile, err->message);
    535     g_error_free(err);
    536     goto done;
    537   }
    538 
    539   if(0 > rename(tmpfile, file)) {
    540     *errstr = g_strdup_printf(_("Failed to rename the file %s to %s:\n%s"),
    541                               tmpfile, file, strerror(errno));
    542     goto done;
    543   }
    544 
    545  done:
    546   g_free(file);
    547   g_free(tmpfile);
    548   if(NULL != io)
    549     g_io_channel_unref(io);
    550 
    551   return NULL == *errstr;
     502  cf_writebenc(FILE_STATE, FILE_STATE_TMP, state, errstr);
    552503}
    553504
    554505void
    555 cf_freestate(struct cf_torrentstate *state) {
    556   if(NULL != state->ts_torrent)
    557     g_free(state->ts_torrent);
    558   if(NULL != state->ts_directory)
    559     g_free(state->ts_directory);
     506cf_freestate(benc_val_t *state) {
     507  tr_bencFree(state);
    560508  g_free(state);
    561509}
  • trunk/gtk/conf.h

    r242 r248  
    2929
    3030#include "transmission.h"
    31 
    32 struct cf_torrentstate {
    33   char *ts_torrent;
    34   char *ts_directory;
    35   gboolean ts_paused;
    36 };
     31#include "bencode.h"
    3732
    3833gboolean
     
    4237char *
    4338cf_sockname(void);
    44 gboolean
     39void
    4540cf_loadprefs(char **errstr);
    4641const char *
     
    4843void
    4944cf_setpref(const char *name, const char *value);
    50 gboolean
     45void
    5146cf_saveprefs(char **errstr);
    52 GList *
     47benc_val_t *
    5348cf_loadstate(char **errstr);
    54 gboolean
    55 cf_savestate(GList *torrents, char **errstr);
    5649void
    57 cf_freestate(struct cf_torrentstate *state);
     50cf_savestate(benc_val_t *state, char **errstr);
     51void
     52cf_freestate(benc_val_t *state);
    5853
    5954#endif /* TG_CONF_H */
  • trunk/gtk/dialogs.c

    r246 r248  
    4646  GtkFileChooser *dir;
    4747  GtkWindow *parent;
    48   tr_handle_t *tr;
     48  TrBackend *back;
    4949};
    5050
    5151struct addcb {
    52   add_torrent_func_t addfunc;
     52  add_torrents_func_t addfunc;
    5353  GtkWindow *parent;
    54   torrents_added_func_t donefunc;
    5554  void *data;
    5655  gboolean autostart;
     
    6564clicklimitbox(GtkWidget *widget, gpointer gdata);
    6665static void
     66freedata(gpointer gdata, GClosure *closure);
     67static void
    6768clickdialog(GtkWidget *widget, int resp, gpointer gdata);
    6869static void
     
    7475
    7576void
    76 makeprefwindow(GtkWindow *parent, tr_handle_t *tr, gboolean *opened) {
     77makeprefwindow(GtkWindow *parent, TrBackend *back, gboolean *opened) {
    7778  char *title = g_strdup_printf(_("%s Preferences"), g_get_application_name());
    7879  GtkWidget *wind = gtk_dialog_new_with_buttons(title, parent,
     
    120121  data->dir = GTK_FILE_CHOOSER(dirstr);
    121122  data->parent = parent;
    122   data->tr = tr;
     123  data->back = back;
     124  g_object_ref(G_OBJECT(back));
    123125
    124126#define RN(n) (n), (n) + 1
     
    176178  gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
    177179  g_signal_connect_data(wind, "response", G_CALLBACK(clickdialog),
    178                         data, (GClosureNotify)g_free, 0);
     180                        data, freedata, 0);
    179181  g_signal_connect(wind, "destroy", G_CALLBACK(windclosed), opened);
    180182  gtk_widget_show_all(wind);
     
    196198    gtk_widget_set_sensitive(widgets[ii],
    197199      gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
     200}
     201
     202static void
     203freedata(gpointer gdata, GClosure *closure SHUTUP) {
     204  struct prefdata *data = gdata;
     205
     206  g_object_unref(G_OBJECT(data->back));
     207  g_free(data);
    198208}
    199209
     
    245255
    246256    /* save prefs */
    247     if(!cf_saveprefs(&errstr)) {
     257    cf_saveprefs(&errstr);
     258    if(NULL != errstr) {
    248259      errmsg(data->parent, "%s", errstr);
    249260      g_free(strnum);
     
    252263
    253264    /* XXX would be nice to have errno strings, are they printed to stdout? */
    254     tr_setBindPort(data->tr, gtk_spin_button_get_value_as_int(data->port));
    255     setlimit(data->tr);
     265    tr_setBindPort(tr_backend_handle(data->back),
     266                   gtk_spin_button_get_value_as_int(data->port));
     267    setlimit(data->back);
    256268  }
    257269
     
    261273
    262274void
    263 setlimit(tr_handle_t *tr) {
     275setlimit(TrBackend *back) {
    264276  struct { void (*func)(tr_handle_t*, int);
    265277    const char *use; const char *num; gboolean defuse; long def; } lim[] = {
     
    271283  const char *pref;
    272284  unsigned int ii;
     285  tr_handle_t *tr = tr_backend_handle(back);
    273286
    274287  for(ii = 0; ii < ALEN(lim); ii++) {
     
    284297
    285298void
    286 makeaddwind(GtkWindow *parent, add_torrent_func_t addfunc,
    287             torrents_added_func_t donefunc, void *cbdata) {
     299makeaddwind(GtkWindow *parent, add_torrents_func_t addfunc, void *cbdata) {
    288300  GtkWidget *wind = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent,
    289301    GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
     
    304316  data->addfunc = addfunc;
    305317  data->parent = parent;
    306   data->donefunc = donefunc;
    307318  data->data = cbdata;
    308319  data->autostart = TRUE;
     
    361372  struct addcb *data = gdata;
    362373  GSList *files, *ii;
    363   gboolean added = FALSE;
    364   char *dir = NULL;
     374  GList *stupidgtk;
     375  gboolean paused;
     376  char *dir;
    365377
    366378  if(GTK_RESPONSE_ACCEPT == resp) {
     379    dir = NULL;
    367380    if(data->usingaltdir)
    368381      dir = gtk_file_chooser_get_filename(data->altdir);
    369382    files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
     383    stupidgtk = NULL;
    370384    for(ii = files; NULL != ii; ii = ii->next)
    371       if(data->addfunc(data->data, ii->data, dir,
    372                        /* XXX need to group errors here */
    373                        !data->autostart, NULL))
    374         added = TRUE;
    375     if(added)
    376       data->donefunc(data->data);
     385      stupidgtk = g_list_append(stupidgtk, ii->data);
     386    paused = !data->autostart;
     387    data->addfunc(data->data, NULL, stupidgtk, dir, &paused);
    377388    if(NULL != dir)
    378389      g_free(dir);
     390    g_slist_free(files);
     391    freestrlist(stupidgtk);
    379392  }
    380393
     
    419432
    420433void
    421 makeinfowind(GtkWindow *parent, tr_torrent_t *tor) {
     434makeinfowind(GtkWindow *parent, TrTorrent *tor) {
    422435  tr_stat_t *sb;
    423436  tr_info_t *in;
     
    428441  GtkWidget *table = gtk_table_new(rowcount, 2, FALSE);
    429442
    430   sb = tr_torrentStat(tor);
    431   in = tr_torrentInfo(tor);
     443  /* XXX should use model and update this window regularly */
     444
     445  sb = tr_torrent_stat(tor);
     446  in = tr_torrent_info(tor);
    432447  str = g_strdup_printf(_("%s Properties"), in->name);
    433448  wind = gtk_dialog_new_with_buttons(str, parent,
     
    475490  INFOSEP(table, ii);
    476491
    477   INFOLINE(table, ii, _("Directory:"), tr_torrentGetFolder(tor));
     492  INFOLINE(table, ii, _("Directory:"), tr_torrentGetFolder(tr_torrent_handle(tor)));
    478493  INFOLINEA(table, ii, _("Downloaded:"), readablesize(sb->downloaded));
    479494  INFOLINEA(table, ii, _("Uploaded:"), readablesize(sb->uploaded));
  • trunk/gtk/dialogs.h

    r242 r248  
    2828#define TG_PREFS_H
    2929
    30 #include "transmission.h"
     30#include "tr_backend.h"
     31#include "tr_torrent.h"
    3132#include "util.h"
    3233
     
    4647
    4748void
    48 makeprefwindow(GtkWindow *parent, tr_handle_t *tr, gboolean *opened);
     49makeprefwindow(GtkWindow *parent, TrBackend *back, gboolean *opened);
    4950
    5051/* set the upload limit based on saved prefs */
    5152void
    52 setlimit(tr_handle_t *tr);
     53setlimit(TrBackend *back);
    5354
    5455/* show the "add a torrent" dialog */
    5556void
    56 makeaddwind(GtkWindow *parent, add_torrent_func_t addfunc,
    57             torrents_added_func_t donefunc, void *cbdata);
     57makeaddwind(GtkWindow *parent, add_torrents_func_t addfunc, void *cbdata);
    5858
    5959/* show the info window for a torrent */
    6060void
    61 makeinfowind(GtkWindow *parent, tr_torrent_t *tor);
     61makeinfowind(GtkWindow *parent, TrTorrent *tor);
    6262
    6363#endif /* TG_PREFS_H */
  • trunk/gtk/ipc.c

    r242 r248  
    3838#include <glib/gi18n.h>
    3939
     40#include "transmission.h"
    4041#include "bencode.h"
     42
    4143#include "conf.h"
    4244#include "io.h"
     
    5557struct constate_serv {
    5658  void *wind;
    57   add_torrent_func_t addfunc;
    58   torrents_added_func_t donefunc;
     59  add_torrents_func_t addfunc;
    5960  void *cbdata;
    6061};
     
    8384
    8485void
    85 ipc_socket_setup(void *parent, add_torrent_func_t addfunc,
    86                  torrents_added_func_t donefunc, void *cbdata);
     86ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata);
    8787gboolean
    8888ipc_sendfiles_blocking(GList *files);
     
    127127
    128128void
    129 ipc_socket_setup(void *parent, add_torrent_func_t addfunc,
    130                  torrents_added_func_t donefunc, void *cbdata) {
     129ipc_socket_setup(void *parent, add_torrents_func_t addfunc, void *cbdata) {
    131130  struct constate *con;
    132131
     
    138137  con->u.serv.wind = parent;
    139138  con->u.serv.addfunc = addfunc;
    140   con->u.serv.donefunc = donefunc;
    141139  con->u.serv.cbdata = cbdata;
    142140 
     
    379377srv_addfile(struct constate *con, const char *name SHUTUP, benc_val_t *val) {
    380378  struct constate_serv *srv = &con->u.serv;
    381   GList *errs = NULL;
    382   char *str;
     379  GList *files;
    383380  int ii;
    384   gboolean added;
    385   benc_val_t *file;
    386381
    387382  if(TYPE_LIST == val->type) {
    388     added = FALSE;
    389     for(ii = 0; ii < val->val.l.count; ii++) {
    390       file = &val->val.l.vals[ii];
    391       if(TYPE_STR == file->type && g_utf8_validate(file->val.s.s, -1, NULL)) {
    392         /* XXX somehow escape invalid utf-8 */
    393         added = TRUE;
    394         srv->addfunc(srv->cbdata, file->val.s.s, NULL, FALSE, &errs);
    395       }
    396     }
    397 
    398     if(NULL != errs) {
    399       str = joinstrlist(errs, "\n");
    400       errmsg(srv->wind, ngettext("Failed to load the torrent file %s",
    401                                  "Failed to load the torrent files:\n%s",
    402                                  g_list_length(errs)), str);
    403       freestrlist(errs);
    404       g_free(str);
    405     }
    406     if(added)
    407       srv->donefunc(srv->cbdata);
     383    files = NULL;
     384    for(ii = 0; ii < val->val.l.count; ii++)
     385      if(TYPE_STR == val->val.l.vals[ii].type &&
     386         /* XXX somehow escape invalid utf-8 */
     387         g_utf8_validate(val->val.l.vals[ii].val.s.s, -1, NULL))
     388        files = g_list_append(files, val->val.l.vals[ii].val.s.s);
     389    srv->addfunc(srv->cbdata, NULL, files, NULL, NULL);
     390    g_list_free(files);
    408391  }
    409392}
  • trunk/gtk/ipc.h

    r242 r248  
    3131
    3232void
    33 ipc_socket_setup(void *wind, add_torrent_func_t addfunc,
    34                  torrents_added_func_t donefunc, void *cbdata);
     33ipc_socket_setup(void *wind, add_torrents_func_t addfunc, void *cbdata);
    3534
    3635gboolean
  • trunk/gtk/main.c

    r246 r248  
    4242#include "dialogs.h"
    4343#include "ipc.h"
     44#include "tr_backend.h"
     45#include "tr_torrent.h"
     46#include "tr_cell_renderer_torrent.h"
    4447#include "transmission.h"
    45 #include "trcellrenderertorrent.h"
    4648#include "util.h"
    4749
     
    4951
    5052struct cbdata {
    51   tr_handle_t *tr;
     53  TrBackend *back;
    5254  GtkWindow *wind;
    5355  GtkTreeModel *model;
     
    5759  guint timer;
    5860  gboolean prefsopen;
     61  GtkWidget *stupidpopuphack;
    5962};
    6063
     
    6568};
    6669
    67 struct pieces {
    68   char p[120];
    69 };
    70 
    7170GList *
    7271readargs(int argc, char **argv);
    7372
    7473void
    75 maketypes(void);
    76 gpointer
    77 tr_pieces_copy(gpointer);
    78 void
    79 tr_pieces_free(gpointer);
    80 
    81 void
    82 makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args);
     74makewind(GtkWidget *wind, TrBackend *back, benc_val_t *state, GList *args);
    8375GtkWidget *
    8476makewind_toolbar(struct cbdata *data);
     
    9082exitcheck(gpointer gdata);
    9183void
    92 stoptransmission(tr_handle_t *tr);
     84stoptransmission(struct cbdata *data);
    9385void
    9486setupdrag(GtkWidget *widget, struct cbdata *data);
     
    114106listpopup(GtkWidget *widget, gpointer gdata);
    115107void
    116 dopopupmenu(GdkEventButton *event, struct cbdata *data,
    117             GList *ids, int status);
    118 void
    119 killmenu(GtkWidget *menu, gpointer *gdata);
     108dopopupmenu(GdkEventButton *event, struct cbdata *data);
    120109void
    121110actionclick(GtkWidget *widget, gpointer gdata);
    122111void
    123 findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter);
     112popupaction(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
     113            gpointer gdata);
    124114gint
    125115intrevcmp(gconstpointer a, gconstpointer b);
     
    128118            gpointer gdata);
    129119
    130 gboolean
    131 addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
    132            GList **errs);
    133 void
    134 addedtorrents(void *vdata);
    135 gboolean
    136 savetorrents(tr_handle_t *tr, GtkWindow *wind);
     120void
     121addtorrents(void *vdata, void *state, GList *files,
     122            const char *dir, gboolean *paused);
     123void
     124savetorrents(struct cbdata *data);
    137125void
    138126orstatus(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
    139127         gpointer gdata);
    140128void
    141 makeidlist(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
    142            gpointer gdata);
    143 void
    144 maketorrentlist(tr_torrent_t *tor, void *data);
     129istorsel(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
     130         gpointer gdata);
    145131void
    146132safepipe(void);
     
    149135void
    150136fatalsig(int sig);
    151 
    152 #define TR_TYPE_PIECES_NAME     "tr-type-pieces"
    153 #define TR_TYPE_PIECES          ((const GType)tr_type_pieces)
    154 #define TR_PIECES(ptr)          ((struct pieces*)ptr)
    155 GType tr_type_pieces;
    156137
    157138#define LIST_ACTION           "torrent-list-action"
    158139enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO, ACT_PREF };
    159 #define LIST_ACTION_FROM      "torrent-list-action-from"
    160 enum listfrom { FROM_BUTTON, FROM_POPUP };
    161 
    162 #define LIST_IDS              "torrent-list-ids"
    163 #define LIST_MENU_WIDGET      "torrent-list-popup-menu-widget"
    164140
    165141struct { const gchar *name; const gchar *id; enum listact act; gboolean nomenu;
     
    193169  GtkWidget *mainwind, *preferr, *stateerr;
    194170  char *err;
    195   tr_handle_t *tr;
    196   GList *saved;
     171  TrBackend *back;
     172  benc_val_t *state;
    197173  const char *pref;
    198174  long intval;
     
    218194
    219195  g_set_application_name(_("Transmission"));
    220 
    221   tr = tr_init();
    222196
    223197  gtk_rc_parse_string(
     
    231205  if(didinit || cf_init(tr_getPrefsDirectory(), &err)) {
    232206    if(didlock || cf_lock(&err)) {
     207
    233208      /* create main window now so any error dialogs can be it's children */
    234209      mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
     
    236211      stateerr = NULL;
    237212
    238       if(!cf_loadprefs(&err)) {
     213      cf_loadprefs(&err);
     214      if(NULL != err) {
    239215        preferr = errmsg(GTK_WINDOW(mainwind), "%s", err);
    240216        g_free(err);
    241217      }
    242       saved = cf_loadstate(&err);
     218      state = cf_loadstate(&err);
    243219      if(NULL != err) {
    244220        stateerr = errmsg(GTK_WINDOW(mainwind), "%s", err);
     
    246222      }
    247223
     224      back = tr_backend_new();
     225
    248226      /* set the upload limit */
    249       setlimit(tr);
     227      setlimit(back);
    250228
    251229      /* set the listening port */
    252230      if(NULL != (pref = cf_getpref(PREF_PORT)) &&
    253231         0 < (intval = strtol(pref, NULL, 10)) && 0xffff >= intval)
    254         tr_setBindPort(tr, intval);
    255 
    256       maketypes();
    257       makewind(mainwind, tr, saved, argfiles);
     232        tr_setBindPort(tr_backend_handle(back), intval);
     233
     234      makewind(mainwind, back, state, argfiles);
     235
     236      cf_freestate(state);
     237      g_object_unref(back);
    258238
    259239      if(NULL != preferr)
     
    294274
    295275void
    296 maketypes(void) {
    297   tr_type_pieces = g_boxed_type_register_static(
    298     TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
    299 }
    300 
    301 gpointer
    302 tr_pieces_copy(gpointer data) {
    303   return g_memdup(data, sizeof(struct pieces));
    304 }
    305 
    306 void
    307 tr_pieces_free(gpointer data) {
    308   g_free(data);
    309 }
    310 
    311 void
    312 makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved, GList *args) {
     276makewind(GtkWidget *wind, TrBackend *back, benc_val_t *state, GList *args) {
    313277  GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
    314278  GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
     
    317281  GtkWidget *list;
    318282  GtkRequisition req;
    319   GList *loaderrs, *ii;
    320   struct cf_torrentstate *ts;
    321283  gint height;
    322   char *str;
    323 
    324   data->tr = tr;
     284
     285  g_object_ref(G_OBJECT(back));
     286  data->back = back;
    325287  data->wind = GTK_WINDOW(wind);
    326288  data->timer = -1;
     
    331293  data->buttons = NULL;
    332294  data->prefsopen = FALSE;
     295  data->stupidpopuphack = NULL;
    333296
    334297  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER,
     
    350313  setupdrag(list, data);
    351314
    352   loaderrs = NULL;
    353   for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
    354     ts = ii->data;
    355     addtorrent(data, ts->ts_torrent, ts->ts_directory, ts->ts_paused,
    356                &loaderrs);
    357     cf_freestate(ts);
    358   }
    359   g_list_free(saved);
    360 
    361   for(ii = g_list_first(args); NULL != ii; ii = ii->next)
    362     addtorrent(data, ii->data, NULL, FALSE, &loaderrs);
    363 
    364   if(NULL != loaderrs) {
    365     str = joinstrlist(loaderrs, "\n");
    366     errmsg(GTK_WINDOW(wind), ngettext("Failed to load the torrent file %s",
    367                                       "Failed to load the torrent files:\n%s",
    368                                       g_list_length(loaderrs)), str);
    369     g_list_foreach(loaderrs, (GFunc)g_free, NULL);
    370     g_list_free(loaderrs);
    371     g_free(str);
    372     savetorrents(tr, GTK_WINDOW(wind));
    373   }
     315  addtorrents(data, state, args, NULL, NULL);
    374316
    375317  data->timer = g_timeout_add(500, updatemodel, data);
     
    390332  gtk_widget_show(wind);
    391333
    392   ipc_socket_setup(GTK_WINDOW(wind), addtorrent, addedtorrents, data);
     334  ipc_socket_setup(GTK_WINDOW(wind), addtorrents, data);
    393335}
    394336
     
    414356    g_object_set_data(G_OBJECT(item), LIST_ACTION,
    415357                      GINT_TO_POINTER(actionitems[ii].act));
    416     g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
    417                       GINT_TO_POINTER(FROM_BUTTON));
    418358    g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
    419359    gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1);
     
    424364
    425365/* XXX check for unused data in model */
    426 enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR, MC_PROG, MC_DRATE, MC_URATE,
    427       MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
    428       MC_TORRENT, MC_ROW_COUNT};
     366enum {
     367  MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR,
     368  MC_PROG, MC_DRATE, MC_URATE, MC_ETA, MC_PEERS,
     369  MC_UPEERS, MC_DPEERS, MC_DOWN, MC_UP,
     370  MC_TORRENT, MC_ROW_COUNT,
     371};
    429372
    430373GtkWidget *
     
    435378    /* progress,  rateDownload, rateUpload,   eta,        peersTotal, */
    436379    G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
    437     /* peersUploading, peersDownloading, pieces,         downloaded, */
    438     G_TYPE_INT,        G_TYPE_INT,       TR_TYPE_PIECES, G_TYPE_UINT64,
    439     /* uploaded,   the handle for the torrent */
    440     G_TYPE_UINT64, G_TYPE_POINTER};
     380    /* peersUploading, peersDownloading, downloaded,    uploaded */
     381    G_TYPE_INT,        G_TYPE_INT,       G_TYPE_UINT64, G_TYPE_UINT64,
     382    /* the torrent object */
     383    TR_TORRENT_TYPE};
    441384  GtkListStore *store;
    442385  GtkWidget *view;
     
    493436  tr_stat_t *st;
    494437  GtkTreeIter iter;
    495   tr_torrent_t *tor;
     438  TrTorrent *tor;
    496439  gboolean going;
    497440
     
    503446  while(going) {
    504447    gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
    505     st = tr_torrentStat(tor);
     448    st = tr_torrent_stat(tor);
    506449    if(TR_STATUS_ACTIVE & st->status) {
    507       tr_torrentStop(tor);
     450      tr_torrentStop(tr_torrent_handle(tor));
    508451      going = gtk_tree_model_iter_next(data->model, &iter);
    509452    } else {
    510       tr_torrentClose(data->tr, tor);
    511453      going = gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
    512454    }
     455    g_object_unref(G_OBJECT(tor));
    513456  }
    514457
     
    530473  tr_stat_t *st;
    531474  GtkTreeIter iter;
    532   tr_torrent_t *tor;
     475  TrTorrent *tor;
    533476  gboolean go;
    534477
     
    536479  while(go) {
    537480    gtk_tree_model_get(data->cbdata->model, &iter, MC_TORRENT, &tor, -1);
    538     st = tr_torrentStat(tor);
     481    st = tr_torrent_stat(tor);
    539482    if(!(TR_STATUS_PAUSE & st->status))
    540483      go = gtk_tree_model_iter_next(data->cbdata->model, &iter);
    541484    else {
    542       tr_torrentClose(data->cbdata->tr, tor);
    543485      go = gtk_list_store_remove(GTK_LIST_STORE(data->cbdata->model), &iter);
    544486    }
     487    g_object_unref(G_OBJECT(tor));
    545488  }
    546489
    547490  /* keep going if we still have torrents and haven't hit the exit timeout */
    548   if(0 < tr_torrentCount(data->cbdata->tr) &&
     491  if(0 < tr_torrentCount(tr_backend_handle(data->cbdata->back)) &&
    549492     time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
    550493    assert(gtk_tree_model_get_iter_first(data->cbdata->model, &iter));
     
    559502  data->timer = -1;
    560503
    561   stoptransmission(data->cbdata->tr);
     504  stoptransmission(data->cbdata);
    562505
    563506  gtk_widget_destroy(GTK_WIDGET(data->cbdata->wind));
     507  if(NULL != data->cbdata->stupidpopuphack)
     508    gtk_widget_destroy(data->cbdata->stupidpopuphack);
     509  g_free(data->cbdata->buttons);
    564510  g_free(data->cbdata);
    565511  g_free(data);
     
    570516
    571517void
    572 stoptransmission(tr_handle_t *tr) {
    573   GList *list, *ii;
    574 
    575   list = NULL;
    576   tr_torrentIterate(tr, maketorrentlist, &list);
    577   for(ii = g_list_first(list); NULL != ii; ii = ii->next)
    578     tr_torrentClose(tr, ii->data);
    579   g_list_free(list);
    580 
    581   tr_close(tr);
     518stoptransmission(struct cbdata *data) {
     519  GtkTreeIter iter;
     520  TrTorrent *tor;
     521  gboolean go;
     522
     523  go = gtk_tree_model_get_iter_first(data->model, &iter);
     524  while(go) {
     525    gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
     526    go = gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
     527    g_object_unref(G_OBJECT(tor));
     528  }
     529  g_assert(0 == tr_torrentCount(tr_backend_handle(data->back)));
     530
     531  g_object_unref(G_OBJECT(data->back));
    582532}
    583533
     
    591541  int ii, len;
    592542  GList *errs;
    593   gboolean gotfile;
    594543  struct stat sb;
    595544  int prelen = strlen(prefix);
     545  GList *paths;
    596546
    597547#ifdef DND_DEBUG
     
    613563
    614564  errs = NULL;
    615   gotfile = FALSE;
     565  paths = NULL;
    616566  if(gdk_atom_intern("XdndSelection", FALSE) == sel->selection &&
    617567     8 == sel->format) {
     
    644594               0 == g_stat(hostless, &sb))
    645595              deslashed = hostless;
    646             /* finally, try to add it as a torrent */
    647             if(addtorrent(data, deslashed, NULL, FALSE, &errs))
    648               gotfile = TRUE;
     596            /* finally, add it to the list of torrents to try adding */
     597            paths = g_list_append(paths, deslashed);
    649598          }
    650599        }
     
    653602    }
    654603
     604    /* try to add any torrents we found */
     605    if(NULL != paths) {
     606      addtorrents(data, NULL, paths, NULL, NULL);
     607      freestrlist(paths);
     608    }
    655609    g_free(files);
    656     if(gotfile)
    657       addedtorrents(data);
    658 
    659     /* print any errors */
    660     if(NULL != errs) {
    661       files = joinstrlist(errs, "\n");
    662       errmsg(data->wind, ngettext("Failed to load the torrent file %s",
    663                                   "Failed to load the torrent files:\n%s",
    664                                   g_list_length(errs)), files);
    665       g_list_foreach(errs, (GFunc)g_free, NULL);
    666       g_list_free(errs);
    667       g_free(files);
    668     }
    669   }
    670 
    671   gtk_drag_finish(dc, gotfile, FALSE, time);
     610  }
     611
     612  gtk_drag_finish(dc, (NULL != paths), FALSE, time);
    672613}
    673614
     
    777718  g_free(str);
    778719  g_free(top);
    779   g_free(bottom);
     720  if(NULL != bottom)
     721    g_free(bottom);
    780722}
    781723
     
    813755updatemodel(gpointer gdata) {
    814756  struct cbdata *data = gdata;
    815   tr_torrent_t *tor;
     757  TrTorrent *tor;
    816758  tr_stat_t *st;
    817759  tr_info_t *in;
     
    821763
    822764  if(0 < global_sigcount) {
    823     stoptransmission(data->tr);
     765    stoptransmission(data);
    824766    global_sigcount = SIGCOUNT_MAX;
    825767    raise(global_lastsig);
     
    829771    do {
    830772      gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
    831       st = tr_torrentStat(tor);
    832       in = tr_torrentInfo(tor);
     773      st = tr_torrent_stat(tor);
     774      in = tr_torrent_info(tor);
     775      g_object_unref(tor);
    833776      /* XXX find out if setting the same data emits changed signal */
    834777      gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
     
    843786
    844787  /* update the status bar */
    845   tr_torrentRates(data->tr, &up, &down);
     788  tr_torrentRates(tr_backend_handle(data->back), &up, &down);
    846789  downstr = readablesize(down * 1024.0);
    847790  upstr = readablesize(up * 1024.0);
     
    868811  GtkTreeIter iter;
    869812  int status;
    870   gpointer tor;
    871   GList *ids;
     813  TrTorrent *tor, *issel;
    872814
    873815  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
     
    875817    if(!gtk_tree_view_get_path_at_pos(data->view, event->x, event->y, &path,
    876818                                      NULL, NULL, NULL))
    877       /* no row was clicked, do the popup with no torrent IDs or status */
    878       dopopupmenu(event, data, NULL, 0);
     819      gtk_tree_selection_unselect_all(sel);
    879820    else {
    880821      if(gtk_tree_model_get_iter(data->model, &iter, path)) {
    881         /* get ID and status for the right-clicked row */
     822        /* get torrent and status for the right-clicked row */
    882823        gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
    883824                           MC_STAT, &status, -1);
    884         /* get a list of selected IDs */
    885         ids = NULL;
    886         gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
    887         /* is the clicked row selected? */
    888         if(NULL == g_list_find(ids, tor)) {
    889           /* no, do the popup for just the clicked row */
    890           g_list_free(ids);
    891           dopopupmenu(event, data, g_list_append(NULL, tor), status);
    892         } else {
    893           /* yes, do the popup for all the selected rows */
    894           gtk_tree_selection_selected_foreach(sel, orstatus, &status);
    895           dopopupmenu(event, data, ids, status);
     825        issel = tor;
     826        gtk_tree_selection_selected_foreach(sel, istorsel, &issel);
     827        g_object_unref(tor);
     828        /* if the clicked row isn't selected, select only it */
     829        if(NULL != issel) {
     830          gtk_tree_selection_unselect_all(sel);
     831          gtk_tree_selection_select_iter(sel, &iter);
    896832        }
    897833      }
    898834      gtk_tree_path_free(path);
    899835    }
     836    dopopupmenu(event, data);
    900837    return TRUE;
    901838  }
     
    906843gboolean
    907844listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
    908   struct cbdata *data = gdata;
     845  dopopupmenu(NULL, gdata);
     846  return TRUE;
     847}
     848
     849void
     850dopopupmenu(GdkEventButton *event, struct cbdata *data) {
    909851  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
    910   GList *ids;
    911   int status;
    912 
    913   if(0 >= gtk_tree_selection_count_selected_rows(sel))
    914     dopopupmenu(NULL, data, NULL, 0);
    915   else {
    916     status = 0;
    917     gtk_tree_selection_selected_foreach(sel, orstatus, &status);
    918     ids = NULL;
    919     gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
    920     dopopupmenu(NULL, data, ids, status);
    921   }
    922 
    923   return TRUE;
    924 }
    925 
    926 void
    927 dopopupmenu(GdkEventButton *event, struct cbdata *data,
    928             GList *ids, int status) {
     852  int count = gtk_tree_selection_count_selected_rows(sel);
    929853  GtkWidget *menu = gtk_menu_new();
    930854  GtkWidget *item;
    931855  unsigned int ii;
     856  int status = 0;
     857
     858  if(NULL != data->stupidpopuphack)
     859    gtk_widget_destroy(data->stupidpopuphack);
     860  data->stupidpopuphack = menu;
     861
     862  status = 0;
     863  gtk_tree_selection_selected_foreach(sel, orstatus, &status);
    932864
    933865  for(ii = 0; ii < ALEN(actionitems); ii++) {
    934866    if(actionitems[ii].nomenu ||
    935867       (actionitems[ii].avail &&
    936         (NULL == ids || !(actionitems[ii].avail & status))))
     868        (0 == count || !(actionitems[ii].avail & status))))
    937869      continue;
    938870    item = gtk_menu_item_new_with_label(gettext(actionitems[ii].name));
     
    940872    g_object_set_data(G_OBJECT(item), LIST_ACTION,
    941873                      GINT_TO_POINTER(actionitems[ii].act));
    942     /* show that this action came from a popup menu */
    943     g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
    944                       GINT_TO_POINTER(FROM_POPUP));
    945     /* set a glist of selected torrent's IDs */
    946     g_object_set_data(G_OBJECT(item), LIST_IDS, ids);
    947     /* set the menu widget, so the activate handler can destroy it */
    948     g_object_set_data(G_OBJECT(item), LIST_MENU_WIDGET, menu);
    949874    g_signal_connect(G_OBJECT(item), "activate",
    950875                     G_CALLBACK(actionclick), data);
     
    952877  }
    953878
    954   /* set up the glist to be freed when the menu is destroyed */
    955   g_object_set_data_full(G_OBJECT(menu), LIST_IDS, ids,
    956                          (GDestroyNotify)g_list_free);
    957 
    958   /* destroy the menu if the user doesn't select anything */
    959   g_signal_connect(menu, "selection-done", G_CALLBACK(killmenu), NULL);
    960 
    961879  gtk_widget_show_all(menu);
    962880
     
    966884}
    967885
    968 void
    969 killmenu(GtkWidget *menu, gpointer *gdata SHUTUP) {
    970   gtk_widget_destroy(menu);
    971 }
     886struct actioninfo {
     887  GtkWindow *wind;
     888  enum listact act;
     889  unsigned int off;
     890  GtkTreeSelection *sel;
     891  TrBackend *back;
     892  gboolean changed;
     893  GList *dead;
     894};
    972895
    973896void
    974897actionclick(GtkWidget *widget, gpointer gdata) {
    975898  struct cbdata *data = gdata;
     899  struct actioninfo info = {
     900    data->wind,
     901    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION)),
     902    0,
     903    gtk_tree_view_get_selection(data->view),
     904    data->back,
     905    FALSE,
     906    NULL,
     907  };
    976908  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
    977   enum listact act =
    978     GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
    979   enum listfrom from =
    980     GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
    981   unsigned int actindex;
    982   tr_stat_t *sb;
    983   GList *ids, *ii;
    984   tr_torrent_t *tor;
    985   gboolean updatesave;
     909  GList *ii;
     910  GtkTreePath *path;
    986911  GtkTreeIter iter;
    987912
    988   /* destroy the popup menu, if any */
    989   if(FROM_POPUP == from)
    990     gtk_widget_destroy(g_object_get_data(G_OBJECT(widget), LIST_MENU_WIDGET));
    991 
    992   switch(act) {
     913  switch(info.act) {
    993914    case ACT_OPEN:
    994       makeaddwind(data->wind, addtorrent, addedtorrents, data);
     915      makeaddwind(data->wind, addtorrents, data);
    995916      return;
    996917    case ACT_PREF:
    997918      if(!data->prefsopen)
    998         makeprefwindow(data->wind, data->tr, &data->prefsopen);
     919        makeprefwindow(data->wind, data->back, &data->prefsopen);
    999920      return;
    1000     default:
     921    case ACT_START:
     922    case ACT_STOP:
     923    case ACT_DELETE:
     924    case ACT_INFO:
    1001925      break;
    1002926  }
    1003927
    1004   switch(from) {
    1005     case FROM_BUTTON:
    1006       ids = NULL;
    1007       gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
     928  for(info.off = 0; info.off < ALEN(actionitems); info.off++)
     929    if(actionitems[info.off].act == info.act)
    1008930      break;
    1009     case FROM_POPUP:
    1010       ids = g_object_get_data(G_OBJECT(widget), LIST_IDS);
    1011       break;
    1012     default:
    1013       assert(!"unknown action source");
    1014       break;
    1015   }
    1016 
    1017   for(actindex = 0; actindex < ALEN(actionitems); actindex++)
    1018     if(actionitems[actindex].act == act)
    1019       break;
    1020   assert(actindex < ALEN(actionitems));
    1021 
    1022   updatesave = FALSE;
    1023 
    1024   for(ii = g_list_sort(ids, intrevcmp); NULL != ii; ii = ii->next) {
    1025     tor = ii->data;
    1026     sb = tr_torrentStat(tor);
    1027 
    1028     /* check if this action is valid for this torrent */
    1029     if(actionitems[actindex].nomenu ||
    1030        (actionitems[actindex].avail &&
    1031         !(actionitems[actindex].avail & sb->status)))
    1032       continue;
    1033 
    1034     switch(act) {
     931  assert(info.off < ALEN(actionitems));
     932
     933  gtk_tree_selection_selected_foreach(sel, popupaction, &info);
     934
     935  for(ii = info.dead; NULL != ii; ii = ii->next) {
     936    assert(gtk_tree_row_reference_valid(ii->data));
     937    path = gtk_tree_row_reference_get_path(ii->data);
     938    gtk_tree_selection_unselect_path(info.sel, path);
     939    if(gtk_tree_model_get_iter(data->model, &iter, path))
     940       gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
     941    else
     942      assert(!"bad path");
     943    gtk_tree_path_free(path);
     944    gtk_tree_row_reference_free(ii->data);
     945  }
     946  g_list_free(info.dead);
     947
     948  if(info.changed) {
     949    savetorrents(data);
     950    updatemodel(data);
     951  }
     952}
     953
     954void
     955popupaction(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
     956            gpointer gdata) {
     957  struct actioninfo *in = gdata;
     958  TrTorrent *tor;
     959  int status;
     960
     961  gtk_tree_model_get(model, iter, MC_TORRENT, &tor, MC_STAT, &status, -1);
     962
     963  /* check if this action is valid for this torrent */
     964  if((!actionitems[in->off].avail || actionitems[in->off].avail & status) &&
     965     !actionitems[in->off].nomenu) {
     966    switch(in->act) {
    1035967      case ACT_START:
    1036         tr_torrentStart(tor);
    1037         updatesave = TRUE;
     968        tr_torrentStart(tr_torrent_handle(tor));
     969        in->changed = TRUE;
    1038970        break;
    1039971      case ACT_STOP:
    1040         tr_torrentStop(tor);
    1041         updatesave = TRUE;
     972        tr_torrentStop(tr_torrent_handle(tor));
     973        in->changed = TRUE;
    1042974        break;
    1043975      case ACT_DELETE:
    1044         if(TR_STATUS_ACTIVE & sb->status)
    1045           tr_torrentStop(tor);
    1046         tr_torrentClose(data->tr, tor);
    1047         findtorrent(data->model, tor, &iter);
    1048         gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
    1049         updatesave = TRUE;
    1050         /* XXX should only unselect deleted rows */
    1051         gtk_tree_selection_unselect_all(
    1052           gtk_tree_view_get_selection(data->view));
     976        in->dead = g_list_append(in->dead,
     977                                 gtk_tree_row_reference_new(model, path));
     978        in->changed = TRUE;
    1053979        break;
    1054980      case ACT_INFO:
    1055         makeinfowind(data->wind, tor);
     981        makeinfowind(in->wind, tor);
    1056982        break;
    1057       default:
    1058         assert(!"unknown type");
     983      case ACT_OPEN:
     984      case ACT_PREF:
    1059985        break;
    1060986    }
    1061987  }
    1062988
    1063   if(updatesave) {
    1064     savetorrents(data->tr, data->wind);
    1065     updatemodel(data);
    1066   }
    1067 
    1068   if(FROM_BUTTON == from)
    1069     g_list_free(ids);
    1070 }
    1071 
    1072 void
    1073 findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter) {
    1074   gpointer ptr;
    1075 
    1076   if(gtk_tree_model_get_iter_first(model, iter)) {
    1077     do {
    1078       gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
    1079       if(tor == ptr)
    1080         return;
    1081     } while(gtk_tree_model_iter_next(model, iter));
    1082   }
    1083 
    1084   assert(!"torrent not found");
     989  g_object_unref(tor);
    1085990}
    1086991
     
    11031008  struct cbdata *data = gdata;
    11041009  GtkTreeIter iter;
    1105   tr_torrent_t *tor;
     1010  TrTorrent *tor;
    11061011
    11071012  if(gtk_tree_model_get_iter(data->model, &iter, path)) {
    11081013    gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
    11091014    makeinfowind(data->wind, tor);
    1110   }
    1111 }
    1112 
    1113 gboolean
    1114 addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
    1115            GList **errs) {
    1116   const struct { const int err; const char *msg; } errstrs[] = {
    1117     {TR_EINVALID,       N_("not a valid torrent file")},
    1118     {TR_EDUPLICATE,     N_("torrent is already open")},
    1119   };
     1015    g_object_unref(tor);
     1016  }
     1017}
     1018
     1019void
     1020addtorrents(void *vdata, void *state, GList *files,
     1021            const char *dir, gboolean *paused) {
    11201022  struct cbdata *data = vdata;
    1121   tr_torrent_t *new;
     1023  GList *torlist, *errlist, *ii;
     1024  char *errstr;
     1025  TrTorrent *tor;
     1026  GtkTreeIter iter;
    11221027  char *wd;
    1123   int err;
    1124   unsigned int ii;
    1125   GtkTreeIter iter;
    1126 
    1127   if(NULL == dir && NULL != (dir = cf_getpref(PREF_DIR))) {
    1128     if(!mkdir_p(dir, 0777)) {
    1129       errmsg(data->wind, _("Failed to create the directory %s:\n%s"),
    1130              dir, strerror(errno));
    1131       return FALSE;
     1028
     1029  errlist = NULL;
     1030  torlist = NULL;
     1031
     1032  if(NULL != state)
     1033    torlist = tr_backend_load_state(data->back, state, &errlist);
     1034
     1035  if(NULL != files) {
     1036    if(NULL == dir)
     1037      dir = cf_getpref(PREF_DIR);
     1038    wd = NULL;
     1039    if(NULL == dir) {
     1040      wd = g_new(char, MAX_PATH_LENGTH + 1);
     1041      if(NULL == getcwd(wd, MAX_PATH_LENGTH + 1))
     1042        dir = ".";
     1043      else
     1044        dir = wd;
    11321045    }
    1133   }
    1134 
    1135   if(NULL == (new = tr_torrentInit(data->tr, torrent, &err))) {
    1136     for(ii = 0; ii < ALEN(errstrs); ii++)
    1137       if(err == errstrs[ii].err)
    1138         break;
    1139     if(NULL == errs) {
    1140       if(ii == ALEN(errstrs))
    1141         errmsg(data->wind, _("Failed to load the torrent file %s"), torrent);
    1142       else
    1143         errmsg(data->wind, _("Failed to load the torrent file %s: %s"),
    1144                torrent, gettext(errstrs[ii].msg));
    1145     } else {
    1146       if(ii == ALEN(errstrs))
    1147         *errs = g_list_append(*errs, g_strdup(torrent));
    1148       else
    1149         *errs = g_list_append(*errs, g_strdup_printf(_("%s (%s)"),
    1150                               torrent, gettext(errstrs[ii].msg)));
     1046    for(ii = g_list_first(files); NULL != ii; ii = ii->next) {
     1047      errstr = NULL;
     1048      tor = tr_torrent_new(G_OBJECT(data->back), ii->data,
     1049                           dir, paused, &errstr);
     1050      if(NULL != tor)
     1051        torlist = g_list_append(torlist, tor);
     1052      if(NULL != errstr)
     1053        errlist = g_list_append(errlist, errstr);
    11511054    }
    1152     return FALSE;
    1153   }
    1154 
    1155   gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
    1156   gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_TORRENT, new, -1);
    1157 
    1158   if(NULL != dir)
    1159     tr_torrentSetFolder(new, dir);
    1160   else {
    1161     wd = g_new(char, MAXPATHLEN + 1);
    1162     if(NULL == getcwd(wd, MAXPATHLEN + 1))
    1163       tr_torrentSetFolder(new, ".");
    1164     else {
    1165       tr_torrentSetFolder(new, wd);
    1166       free(wd);
    1167     }
    1168   }
    1169 
    1170   if(!paused)
    1171     tr_torrentStart(new);
    1172 
    1173   return TRUE;
    1174 }
    1175 
    1176 void
    1177 addedtorrents(void *vdata) {
    1178   struct cbdata *data = vdata;
    1179 
    1180   updatemodel(data);
    1181   savetorrents(data->tr, data->wind);
    1182 }
    1183 
    1184 gboolean
    1185 savetorrents(tr_handle_t *tr, GtkWindow *wind) {
    1186   GList *torrents;
     1055    if(NULL != wd)
     1056      g_free(wd);
     1057  }
     1058
     1059  for(ii = g_list_first(torlist); NULL != ii; ii = ii->next) {
     1060    gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
     1061    gtk_list_store_set(GTK_LIST_STORE(data->model), &iter,
     1062                       MC_TORRENT, ii->data, -1);
     1063    g_object_unref(ii->data);
     1064  }
     1065
     1066  if(NULL != errlist) {
     1067    errstr = joinstrlist(errlist, "\n");
     1068    errmsg(data->wind, ngettext("Failed to load torrent file:\n%s",
     1069                                "Failed to load torrent files:\n%s",
     1070                                g_list_length(errlist)), errstr);
     1071    g_list_foreach(errlist, (GFunc)g_free, NULL);
     1072    g_list_free(errlist);
     1073    g_free(errstr);
     1074  }
     1075
     1076  if(NULL != torlist) {
     1077    updatemodel(data);
     1078    savetorrents(data);
     1079  }
     1080}
     1081
     1082void
     1083savetorrents(struct cbdata *data) {
    11871084  char *errstr;
    1188   gboolean ret;
    1189 
    1190   torrents = NULL;
    1191   tr_torrentIterate(tr, maketorrentlist, &torrents);
    1192 
    1193   if(!(ret = cf_savestate(torrents, &errstr))) {
    1194     errmsg(wind, "%s", errstr);
     1085
     1086  tr_backend_save_state(data->back, &errstr);
     1087  if(NULL != errstr) {
     1088    errmsg(data->wind, "%s", errstr);
    11951089    g_free(errstr);
    11961090  }
    1197 
    1198   g_list_free(torrents);
    1199 
    1200   return ret;
    12011091}
    12021092
     
    12121102}
    12131103
    1214 void
    1215 makeidlist(GtkTreeModel *model, GtkTreePath *path SHUTUP, GtkTreeIter *iter,
    1216            gpointer gdata) {
    1217   GList **ids = gdata;
    1218   gpointer ptr;
    1219 
    1220   gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
    1221   *ids = g_list_append(*ids, ptr);
    1222 }
    1223 
    1224 void
    1225 maketorrentlist(tr_torrent_t *tor, void *data) {
    1226   GList **list = data;
    1227 
    1228   *list = g_list_append(*list, tor);
     1104/* data should be a TrTorrent**, will set torrent to NULL if it's selected */
     1105void
     1106istorsel(GtkTreeModel *model, GtkTreePath *path SHUTUP, GtkTreeIter *iter,
     1107         gpointer gdata) {
     1108  TrTorrent **torref = gdata;
     1109  TrTorrent *tor;
     1110
     1111  if(NULL != *torref) {
     1112    gtk_tree_model_get(model, iter, MC_TORRENT, &tor, -1);
     1113    if(tor == *torref)
     1114      *torref = NULL;
     1115    g_object_unref(tor);
     1116  }
    12291117}
    12301118
  • trunk/gtk/util.h

    r242 r248  
    3838#endif
    3939
    40 typedef gboolean (*add_torrent_func_t)(void *, const char *, const char *, gboolean, GList **);
    41 typedef void (*torrents_added_func_t)(void *);
     40typedef void (*add_torrents_func_t)(void*,void*,GList*,const char*,gboolean*);
    4241
    4342/* return number of items in array */
Note: See TracChangeset for help on using the changeset viewer.