source: trunk/gtk/main.c @ 20

Last change on this file since 20 was 20, checked in by root, 16 years ago

Update 2005-12-25

File size: 31.0 KB
Line 
1/*
2  Copyright (c) 2005 Joshua Elsasser. All rights reserved.
3   
4  Redistribution and use in source and binary forms, with or without
5  modification, are permitted provided that the following conditions
6  are met:
7   
8   1. Redistributions of source code must retain the above copyright
9      notice, this list of conditions and the following disclaimer.
10   2. Redistributions in binary form must reproduce the above copyright
11      notice, this list of conditions and the following disclaimer in the
12      documentation and/or other materials provided with the distribution.
13   
14  THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
15  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  POSSIBILITY OF SUCH DAMAGE.
25*/
26
27#include <sys/param.h>
28#include <assert.h>
29#include <errno.h>
30#include <string.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <gtk/gtk.h>
37
38#include "conf.h"
39#include "gtkcellrenderertorrent.h"
40#include "prefs.h"
41#include "transmission.h"
42#include "util.h"
43
44#define TRACKER_EXIT_TIMEOUT    5
45
46struct cbdata {
47  tr_handle_t *tr;
48  GtkWindow *wind;
49  GtkListStore *model;
50  GtkTreeView *view;
51  GtkStatusbar *bar;
52  GtkWidget **buttons;
53  guint timer;
54};
55
56struct exitdata {
57  struct cbdata *cbdata;
58  time_t started;
59  guint timer;
60};
61
62struct pieces {
63  char p[120];
64};
65
66void
67maketypes(void);
68gpointer
69tr_pieces_copy(gpointer);
70void
71tr_pieces_free(gpointer);
72
73void
74makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
75gboolean
76winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
77gboolean
78exitcheck(gpointer gdata);
79GtkWidget *
80makewind_toolbar(struct cbdata *data);
81GtkWidget *
82makewind_list(struct cbdata *data);
83static void
84stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata);
85void
86fixbuttons(GtkTreeSelection *sel, gpointer gdata);
87void
88dfname(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
89       GtkTreeIter *iter, gpointer gdata);
90void
91dfprog(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
92       GtkTreeIter *iter, gpointer gdata);
93
94gboolean
95updatemodel(gpointer gdata);
96gboolean
97listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
98gboolean
99listpopup(GtkWidget *widget, gpointer gdata);
100void
101dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter);
102void
103actionclick(GtkWidget *widget, gpointer gdata);
104
105gboolean
106addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
107           const char *dir, gboolean paused);
108void
109addedtorrents(void *vdata);
110const char *
111statusstr(int status);
112void
113makeinfowind(struct cbdata *data, int index);
114gboolean
115savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat);
116
117#define TR_TYPE_PIECES_NAME     "tr-type-pieces"
118#define TR_TYPE_PIECES          ((const GType)tr_type_pieces)
119#define TR_PIECES(ptr)          ((struct pieces*)ptr)
120GType tr_type_pieces;
121
122#define LIST_ACTION           "torrent-list-action"
123enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO, ACT_PREF };
124#define LIST_ACTION_FROM      "torrent-list-action-from"
125enum listfrom { FROM_BUTTON, FROM_POPUP };
126
127#define LIST_INDEX            "torrent-list-index"
128
129struct { const gchar *name; const gchar *id; enum listact act; gboolean nomenu;
130  int avail; const char *ttext; const char *tpriv; }
131actionitems[] = {
132  {"Add",         GTK_STOCK_ADD,          ACT_OPEN,   FALSE,  0,
133   "Add a new torrent file", "XXX"},
134  {"Resume",      GTK_STOCK_MEDIA_PLAY,   ACT_START,  FALSE,
135   (TR_STATUS_STOPPING | TR_STATUS_PAUSE),
136   "Resume a torrent that has been paused", "XXX"},
137  {"Pause",       GTK_STOCK_MEDIA_PAUSE,  ACT_STOP,   FALSE,
138   ~(TR_STATUS_STOPPING | TR_STATUS_PAUSE),
139   "Pause a torrent", "XXX"},
140  {"Remove",      GTK_STOCK_REMOVE,       ACT_DELETE, FALSE, ~0,
141   "Remove a torrent from the list", "XXX"},
142  {"Properties",  GTK_STOCK_PROPERTIES,   ACT_INFO,   FALSE, ~0,
143   "Get additional information for a torrent", "XXX"},
144  {"Preferences", GTK_STOCK_PREFERENCES,  ACT_PREF,   TRUE,   0,
145   "Open preferences dialog", "XXX"},
146};
147
148#define CBDATA_PTR              "callback-data-pointer"
149int
150main(int argc, char **argv) {
151  GtkWidget *mainwind, *preferr, *stateerr;
152  char *err;
153  tr_handle_t *tr;
154  GList *saved;
155  const char *pref;
156  long intval;
157
158  gtk_init(&argc, &argv);
159
160  tr = tr_init();
161
162  if(cf_init(tr_getPrefsDirectory(), &err)) {
163    if(cf_lock(&err)) {
164      /* create main window now so any error dialogs can be it's children */
165      mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
166      preferr = NULL;
167      stateerr = NULL;
168
169      if(!cf_loadprefs(&err)) {
170        preferr = errmsg(GTK_WINDOW(mainwind), "%s", err);
171        g_free(err);
172      }
173      saved = cf_loadstate(&err);
174      if(NULL != err) {
175        stateerr = errmsg(GTK_WINDOW(mainwind), "%s", err);
176        g_free(err);
177      }
178
179      /* set the upload limit */
180      setlimit(tr);
181
182      /* set the listening port */
183      if(NULL != (pref = cf_getpref(PREF_PORT)) &&
184         0 < (intval = strtol(pref, NULL, 10)) && 0xffff >= intval)
185        tr_setBindPort(tr, intval);
186
187      maketypes();
188      makewind(mainwind, tr, saved);
189
190      if(NULL != preferr)
191        gtk_widget_show_all(preferr);
192      if(NULL != stateerr)
193        gtk_widget_show_all(stateerr);
194    } else {
195      gtk_widget_show(errmsg_full(NULL, (errfunc_t)gtk_main_quit,
196                                  NULL, "%s", err));
197      g_free(err);
198    }
199  } else {
200    gtk_widget_show(errmsg_full(NULL, (errfunc_t)gtk_main_quit,
201                                NULL, "%s", err));
202    g_free(err);
203  }
204
205  gtk_main();
206
207  return 0;
208}
209
210void
211maketypes(void) {
212  tr_type_pieces = g_boxed_type_register_static(
213    TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
214}
215
216gpointer
217tr_pieces_copy(gpointer data) {
218  return g_memdup(data, sizeof(struct pieces));
219}
220
221void
222tr_pieces_free(gpointer data) {
223  g_free(data);
224}
225
226void
227makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
228  GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
229  GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
230  GtkWidget *status = gtk_statusbar_new();
231  struct cbdata *data = g_new0(struct cbdata, 1);
232  GtkWidget *list;
233  GtkRequisition req;
234  GList *ii;
235  struct cf_torrentstate *ts;
236  gint height;
237
238  data->tr = tr;
239  data->wind = GTK_WINDOW(wind);
240  data->timer = -1;
241  /* filled in by makewind_list */
242  data->model = NULL;
243  data->view = NULL;
244  data->bar = GTK_STATUSBAR(status);
245  data->buttons = NULL;
246
247  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER,
248                                 GTK_POLICY_AUTOMATIC);
249
250  gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
251
252  list = makewind_list(data);
253  gtk_container_add(GTK_CONTAINER(scroll), list);
254  gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
255
256  gtk_statusbar_push(GTK_STATUSBAR(status), 0, "");
257  gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
258
259  gtk_container_add(GTK_CONTAINER(wind), vbox);
260  g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
261
262  for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
263    ts = ii->data;
264    addtorrent(tr, GTK_WINDOW(wind),
265               ts->ts_torrent, ts->ts_directory, ts->ts_paused);
266    cf_freestate(ts);
267  }
268  g_list_free(saved);
269
270  data->timer = g_timeout_add(500, updatemodel, data);
271  updatemodel(data);
272
273  gtk_widget_show_all(vbox);
274  gtk_widget_realize(wind);
275
276  gtk_widget_size_request(wind, &req);
277  height = req.height;
278  gtk_widget_size_request(scroll, &req);
279  height -= req.height;
280  gtk_widget_size_request(list, &req);
281  height += req.height;
282  gtk_window_set_default_size(GTK_WINDOW(wind), -1, MAX(height, 100));
283
284  gtk_widget_show(wind);
285}
286
287/* XXX is this the right thing to do? */
288#define TR_TORRENT_NEEDS_STOP(t) \
289  ((t) & TR_STATUS_CHECK || (t) & TR_STATUS_DOWNLOAD || (t) & TR_STATUS_SEED)
290
291gboolean
292winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
293  struct cbdata *data = gdata;
294  struct exitdata *edata;
295  tr_stat_t *st;
296  int ii;
297
298  if(0 >= data->timer)
299    g_source_remove(data->timer);
300  data->timer = -1;
301
302  for(ii = tr_torrentStat(data->tr, &st); 0 < ii; ii--) {
303    if(TR_TORRENT_NEEDS_STOP(st[ii-1].status)) {
304      /*fprintf(stderr, "quit: stopping %i %s\n", ii, st[ii-1].info.name);*/
305      tr_torrentStop(data->tr, ii - 1);
306    } else {
307      /*fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);*/
308      tr_torrentClose(data->tr, ii - 1);
309    }
310  }
311  free(st);
312
313  /* XXX should disable widgets or something */
314
315  /* try to wait until torrents stop before exiting */
316  edata = g_new0(struct exitdata, 1);
317  edata->cbdata = data;
318  edata->started = time(NULL);
319  edata->timer = g_timeout_add(500, exitcheck, edata);
320
321  /*fprintf(stderr, "quit: starting timeout at %i\n", edata->started);*/
322
323  /* returning FALSE means to destroy the window */
324  return TRUE;
325}
326
327gboolean
328exitcheck(gpointer gdata) {
329  struct exitdata *data = gdata;
330  tr_stat_t *st;
331  int ii;
332
333  for(ii = tr_torrentStat(data->cbdata->tr, &st); 0 < ii; ii--) {
334    if(TR_STATUS_PAUSE == st[ii-1].status) {
335      /*fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);*/
336      tr_torrentClose(data->cbdata->tr, ii - 1);
337    }
338  }
339  free(st);
340
341  /*fprintf(stderr, "quit: %i torrents left at %i\n",
342    tr_torrentCount(data->cbdata->tr), time(NULL));*/
343  /* keep going if we still have torrents and haven't hit the exit timeout */
344  if(0 < tr_torrentCount(data->cbdata->tr) &&
345     time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
346    updatemodel(data->cbdata);
347    return TRUE;
348  }
349
350  /*fprintf(stderr, "quit: giving up on %i torrents\n",
351    tr_torrentCount(data->cbdata->tr));*/
352
353  for(ii = tr_torrentCount(data->cbdata->tr); 0 < ii; ii--)
354    tr_torrentClose(data->cbdata->tr, ii - 1);
355
356  /* exit otherwise */
357
358  if(0 >= data->timer)
359    g_source_remove(data->timer);
360  data->timer = -1;
361
362  gtk_widget_destroy(GTK_WIDGET(data->cbdata->wind));
363  tr_close(data->cbdata->tr);
364  g_free(data->cbdata);
365  g_free(data);
366  gtk_main_quit();
367
368  return FALSE;
369}
370
371GtkWidget *
372makewind_toolbar(struct cbdata *data) {
373  GtkWidget *bar = gtk_toolbar_new();
374  GtkToolItem *item;
375  unsigned int ii;
376
377  gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
378  gtk_toolbar_set_show_arrow(GTK_TOOLBAR(bar), FALSE);
379  gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
380
381  data->buttons = g_new(GtkWidget*, ALEN(actionitems));
382
383  for(ii = 0; ii < ALEN(actionitems); ii++) {
384    item = gtk_tool_button_new_from_stock(actionitems[ii].id);
385    data->buttons[ii] = GTK_WIDGET(item);
386    gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), actionitems[ii].name);
387    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
388                              actionitems[ii].ttext, actionitems[ii].tpriv);
389    g_object_set_data(G_OBJECT(item), LIST_ACTION,
390                      GINT_TO_POINTER(actionitems[ii].act));
391    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
392                      GINT_TO_POINTER(FROM_BUTTON));
393    g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
394    gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1);
395  }
396
397  return bar;
398}
399
400/* XXX check for unused data in model */
401enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
402      MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
403      MC_ROW_INDEX, MC_ROW_COUNT};
404
405GtkWidget *
406makewind_list(struct cbdata *data) {
407  GType types[] = {
408    /* info->name, info->totalSize, status,     error,         progress */
409    G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT,
410    /* rateDownload, rateUpload,   eta,        peersTotal, peersUploading */
411    G_TYPE_FLOAT,    G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
412    /* peersDownloading, pieces,         downloaded,    uploaded */
413    G_TYPE_INT,          TR_TYPE_PIECES, G_TYPE_UINT64, G_TYPE_UINT64,
414    /* index into the torrent array */
415    G_TYPE_INT};
416  GtkListStore *model;
417  GtkWidget *view;
418  GtkTreeViewColumn *col;
419  GtkTreeSelection *sel;
420  GtkCellRenderer *namerend, *progrend;
421
422  assert(MC_ROW_COUNT == ALEN(types));
423
424  model = gtk_list_store_newv(MC_ROW_COUNT, types);
425  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
426  /* XXX do I need to worry about reference counts anywhere else? */
427  g_object_unref(G_OBJECT(model));
428  data->model = model;
429  data->view = GTK_TREE_VIEW(view);
430
431  namerend = gtk_cell_renderer_text_new();
432  col = gtk_tree_view_column_new_with_attributes("Name", namerend, NULL);
433  gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
434  gtk_tree_view_column_set_expand(col, TRUE);
435  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
436
437  progrend = gtk_cell_renderer_torrent_new();
438  g_object_set(progrend, "label", "<big>  fnord    fnord  </big>", NULL);
439  col = gtk_tree_view_column_new_with_attributes("Progress", progrend, NULL);
440  gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
441  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
442
443  /* XXX this shouldn't be necessary */
444  g_signal_connect(view, "notify", G_CALLBACK(stylekludge), progrend);
445
446  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
447  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
448  gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
449  g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
450  g_signal_connect(G_OBJECT(view), "button-press-event",
451                   G_CALLBACK(listclick), data);
452  g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
453  gtk_widget_show_all(view);
454
455  return view;
456}
457
458/* kludge to have the progress bars notice theme changes */
459static void
460stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata) {
461  if(0 == strcmp("style", spec->name)) {
462    gtk_cell_renderer_torrent_reset_style(GTK_CELL_RENDERER_TORRENT(gdata));
463    gtk_widget_queue_draw(GTK_WIDGET(obj));
464  }
465}
466
467/* disable buttons the user shouldn't be able to click on */
468void
469fixbuttons(GtkTreeSelection *sel, gpointer gdata) {
470  struct cbdata *data = gdata;
471  GtkTreeModel *model;
472  GtkTreeIter iter;
473  gboolean selected;
474  unsigned int ii;
475  int status;
476
477  if(NULL == sel)
478    sel = gtk_tree_view_get_selection(data->view);
479  if((selected = gtk_tree_selection_get_selected(sel, &model, &iter)))
480    gtk_tree_model_get(model, &iter, MC_STAT, &status, -1);
481
482  for(ii = 0; ii < ALEN(actionitems); ii++)
483    if(actionitems[ii].avail)
484      gtk_widget_set_sensitive(data->buttons[ii],
485                               (selected && (actionitems[ii].avail & status)));
486}
487
488void
489dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
490       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
491  char *name, *mb, *err, *str, *top, *bottom;
492  guint64 size;
493  gfloat prog;
494  int status, eta, tpeers, upeers, dpeers;
495
496  /* XXX should I worry about gtk_tree_model_get failing? */
497  gtk_tree_model_get(model, iter, MC_NAME, &name, MC_STAT, &status,
498    MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta, MC_PEERS, &tpeers,
499    MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
500
501  if(0 > eta)
502    eta = 0;
503  if(0 > tpeers)
504    tpeers = 0;
505  if(0 > upeers)
506    upeers = 0;
507  if(0 > dpeers)
508    dpeers = 0;
509  mb = readablesize(size, 1);
510  prog *= 100;
511
512  if(status & TR_STATUS_CHECK)
513    top = g_strdup_printf("Checking existing files (%.1f%%)", prog);
514  else if(status & TR_STATUS_DOWNLOAD)
515    top = g_strdup_printf("Finishing in %02i:%02i:%02i (%.1f%%)",
516                           eta / 60 / 60, eta / 60 % 60, eta % 60, prog);
517  else if(status & TR_STATUS_SEED)
518    top = g_strdup_printf("Seeding, uploading to %d of %d peer%s",
519                           dpeers, tpeers, (1 == tpeers ? "" : "s"));
520  else if(status & TR_STATUS_STOPPING)
521    top = g_strdup("Stopping...");
522  else if(status & TR_STATUS_PAUSE)
523    top = g_strdup_printf("Paused (%.1f%%)", prog);
524  else {
525    top = g_strdup("");
526    assert("XXX unknown status");
527  }
528
529  if(status & TR_TRACKER_ERROR) {
530    gtk_tree_model_get(model, iter, MC_ERR, &err, -1);
531    bottom = g_strconcat("Error: ", err, NULL);
532    g_free(err);
533  }
534  else if(status & TR_STATUS_DOWNLOAD)
535    bottom = g_strdup_printf("Downloading from %i of %i peer%s",
536                             upeers, tpeers, (1 == tpeers ? "" : "s"));
537  else
538    bottom = NULL;
539
540  str = g_markup_printf_escaped("<big>%s (%s)</big>\n<small>%s\n%s</small>",
541                                name, mb, top, (NULL == bottom ? "" : bottom));
542  g_object_set(rend, "markup", str, NULL);
543  g_free(name);
544  g_free(mb);
545  g_free(str);
546  g_free(top);
547  g_free(bottom);
548}
549
550void
551dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
552       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
553  char *dlstr, *ulstr, *str;
554  gfloat prog, dl, ul;
555
556  /* XXX should I worry about gtk_tree_model_get failing? */
557  gtk_tree_model_get(model, iter, MC_PROG, &prog,
558                     MC_DRATE, &dl, MC_URATE, &ul, -1);
559  if(0.0 > prog)
560    prog = 0.0;
561  else if(1.0 < prog)
562    prog = 1.0;
563
564  dlstr = readablesize(dl * 1024.0, 2);
565  ulstr = readablesize(ul * 1024.0, 2);
566  str = g_strdup_printf("<small>DL: %s/s\nUL: %s/s</small>", dlstr, ulstr);
567  g_object_set(rend, "text", str, "value", prog, NULL);
568  g_free(dlstr);
569  g_free(ulstr);
570  g_free(str);
571}
572
573gboolean
574updatemodel(gpointer gdata) {
575  struct cbdata *data = gdata;
576  tr_stat_t *st;
577  int ii, max;
578  GtkTreeIter iter;
579  float up, down;
580  char *upstr, *downstr, *str;
581
582  max = tr_torrentStat(data->tr, &st);
583  for(ii = 0; ii < max; ii++) {
584    if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
585         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
586      gtk_list_store_append(data->model, &iter);
587    /* XXX find out if setting the same data emits changed signal */
588    gtk_list_store_set(
589      data->model, &iter, MC_ROW_INDEX, ii,
590      MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize,
591      MC_STAT, st[ii].status, MC_ERR, st[ii].error, MC_PROG, st[ii].progress,
592      MC_DRATE, st[ii].rateDownload, MC_URATE, st[ii].rateUpload,
593      MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
594      MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
595      MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
596  }
597  free(st);
598
599  /* remove any excess rows */
600  if(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
601     gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter))
602    while(gtk_list_store_remove(data->model, &iter))
603      ;
604
605  /* update the status bar */
606  tr_torrentRates(data->tr, &up, &down);
607  downstr = readablesize(down * 1024.0, 2);
608  upstr = readablesize(up * 1024.0, 2);
609  str = g_strdup_printf("     Total DL: %s/s     Total UL: %s/s", upstr, downstr);
610  gtk_statusbar_pop(data->bar, 0);
611  gtk_statusbar_push(data->bar, 0, str);
612  g_free(str);
613  g_free(upstr);
614  g_free(downstr);
615
616  /* the status of the selected item may have changed, so update the buttons */
617  fixbuttons(NULL, data);
618
619  return TRUE;
620}
621
622gboolean
623listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
624  struct cbdata *data = gdata;
625  GtkTreePath *path;
626  GtkTreeIter iter;
627
628  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
629    if(!gtk_tree_view_get_path_at_pos(data->view, event->x, event->y, &path,
630                                      NULL, NULL, NULL))
631      dopopupmenu(event, data, NULL);
632    else {
633      if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
634        dopopupmenu(event, data, &iter);
635      gtk_tree_path_free(path);
636    }
637    return TRUE;
638  }
639
640  return FALSE;
641}
642
643gboolean
644listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
645  struct cbdata *data = gdata;
646  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
647  GtkTreeModel *model;
648  GtkTreeIter iter;
649
650  if(!gtk_tree_selection_get_selected(sel, &model, &iter))
651    dopopupmenu(NULL, data, NULL);
652  else {
653    assert(model == GTK_TREE_MODEL(data->model));
654    dopopupmenu(NULL, data, &iter);
655  }
656
657  return TRUE;
658}
659
660void
661dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter) {
662  GtkWidget *menu = gtk_menu_new();
663  GtkWidget *item;
664  unsigned int ii;
665  int index, status;
666
667  index = -1;
668  if(NULL != iter)
669    gtk_tree_model_get(GTK_TREE_MODEL(data->model), iter,
670                       MC_ROW_INDEX, &index, MC_STAT, &status, -1);
671
672  /* XXX am I leaking references here? */
673  for(ii = 0; ii < ALEN(actionitems); ii++) {
674    if(actionitems[ii].nomenu ||
675       (actionitems[ii].avail &&
676        (0 > index || !(actionitems[ii].avail & status))))
677      continue;
678    item = gtk_menu_item_new_with_label(actionitems[ii].name);
679    g_object_set_data(G_OBJECT(item), LIST_ACTION,
680                      GINT_TO_POINTER(actionitems[ii].act));
681    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
682                      GINT_TO_POINTER(FROM_POPUP));
683    g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
684    g_signal_connect(G_OBJECT(item), "activate",
685                     G_CALLBACK(actionclick), data);
686    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
687  }
688
689  gtk_widget_show_all(menu);
690
691  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
692                 (NULL == event ? 0 : event->button),
693                 gdk_event_get_time((GdkEvent*)event));
694}
695
696void
697actionclick(GtkWidget *widget, gpointer gdata) {
698  struct cbdata *data = gdata;
699  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
700  GtkTreeModel *model;
701  GtkTreeIter iter;
702  enum listact act =
703    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
704  enum listfrom from =
705    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
706  int index;
707  tr_stat_t *sb;
708
709  switch(act) {
710    case ACT_OPEN:
711      makeaddwind(addtorrent, data->wind, data->tr, addedtorrents, data);
712      return;
713    case ACT_PREF:
714      makeprefwindow(data->wind, data->tr);
715      return;
716    default:
717      break;
718  }
719
720  index = -1;
721  switch(from) {
722    case FROM_BUTTON:
723      if(gtk_tree_selection_get_selected(sel, &model, &iter))
724        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
725                           MC_ROW_INDEX, &index, -1);
726      /* XXX should I assert(0 <= index) to insure a row was selected? */
727      break;
728    case FROM_POPUP:
729      index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
730      break;
731    default:
732      assert(!"unknown action source");
733      break;
734  }
735
736  if(0 <= index) {
737    switch(act) {
738      case ACT_START:
739        tr_torrentStart(data->tr, index);
740        savetorrents(data->tr, data->wind, -1, NULL);
741        updatemodel(data);
742        break;
743      case ACT_STOP:
744        tr_torrentStop(data->tr, index);
745        savetorrents(data->tr, data->wind, -1, NULL);
746        updatemodel(data);
747        break;
748      case ACT_DELETE:
749        /* XXX need to be able to stat just one torrent */
750        if(index >= tr_torrentStat(data->tr, &sb)) {
751          assert(!"XXX i'm tired");
752        }
753        if(TR_TORRENT_NEEDS_STOP(sb[index].status))
754          tr_torrentStop(data->tr, index);
755        free(sb);
756        tr_torrentClose(data->tr, index);
757        savetorrents(data->tr, data->wind, -1, NULL);
758        updatemodel(data);
759        break;
760      case ACT_INFO:
761        makeinfowind(data, index);
762        break;
763      default:
764        assert(!"unknown type");
765        break;
766    }
767  }
768}
769
770gboolean
771addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
772           const char *dir, gboolean paused) {
773  char *wd;
774
775  if(NULL == dir && NULL != (dir = cf_getpref(PREF_DIR))) {
776    if(!mkdir_p(dir, 0777)) {
777      errmsg(parentwind, "Failed to create download directory %s:\n%s",
778             dir, strerror(errno));
779      return FALSE;
780    }
781  }
782
783  if(0 != tr_torrentInit(tr, torrent)) {
784    /* XXX would be nice to have errno strings, are they printed to stdout? */
785    errmsg(parentwind, "Failed to open torrent file %s", torrent);
786    return FALSE;
787  }
788
789  if(NULL != dir)
790    tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
791  else {
792    wd = g_new(char, MAXPATHLEN + 1);
793    if(NULL == getcwd(wd, MAXPATHLEN + 1))
794      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
795    else {
796      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
797      free(wd);
798    }
799  }
800
801  if(!paused)
802    tr_torrentStart(tr, tr_torrentCount(tr) - 1);
803  return TRUE;
804}
805
806void
807addedtorrents(void *vdata) {
808  struct cbdata *data = vdata;
809
810  updatemodel(data);
811  savetorrents(data->tr, data->wind, -1, NULL);
812}
813
814const char *
815statusstr(int status) {
816  switch(status) {
817    case TR_STATUS_CHECK:       return "check";
818    case TR_STATUS_DOWNLOAD:    return "download";
819    case TR_STATUS_SEED:        return "seed";
820    case TR_STATUS_STOPPING:    return "stopping";
821    case TR_STATUS_STOPPED:     return "stopped";
822    case TR_STATUS_PAUSE:       return "pause";
823    case TR_TRACKER_ERROR:      return "error";
824    default:
825      assert(!"unknown status code");
826      return NULL;
827  }
828}
829
830void
831makeinfowind(struct cbdata *data, int index) {
832  tr_stat_t *sb;
833  GtkWidget *wind, *table, *name, *value;
834  char buf[32];
835
836  if(index >= tr_torrentStat(data->tr, &sb)) {
837    assert(!"XXX i'm tired");
838  }
839  wind = gtk_dialog_new_with_buttons(sb[index].info.name, data->wind,
840    GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
841
842  table = gtk_table_new(21, 2, FALSE);
843
844  name = gtk_label_new("Torrent File");
845  value = gtk_label_new(sb[index].info.torrent);
846  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
847  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
848  name = gtk_label_new("Name");
849  value = gtk_label_new(sb[index].info.name);
850  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
851  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
852  name = gtk_label_new("Tracker Address");
853  value = gtk_label_new(sb[index].info.trackerAddress);
854  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
855  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
856  name = gtk_label_new("Tracker Port");
857  snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
858  value = gtk_label_new(buf);
859  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
860  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
861  name = gtk_label_new("Tracker Announce URL");
862  value = gtk_label_new(sb[index].info.trackerAnnounce);
863  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
864  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
865  name = gtk_label_new("Piece Size");
866  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
867  value = gtk_label_new(buf);
868  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
869  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
870  name = gtk_label_new("Piece Count");
871  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
872  value = gtk_label_new(buf);
873  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
874  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
875  name = gtk_label_new("Total Size");
876  snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
877  value = gtk_label_new(buf);
878  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
879  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
880  name = gtk_label_new("File Count");
881  snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
882  value = gtk_label_new(buf);
883  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
884  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
885  name = gtk_label_new("Status");
886  value = gtk_label_new(statusstr(sb[index].status));
887  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
888  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
889  name = gtk_label_new("Error");
890  value = gtk_label_new(sb[index].error);
891  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
892  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
893  name = gtk_label_new("Progress");
894  snprintf(buf, sizeof buf, "%f", sb[index].progress);
895  value = gtk_label_new(buf);
896  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
897  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
898  name = gtk_label_new("Download Rate");
899  value = gtk_label_new(buf);
900  snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
901  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
902  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
903  name = gtk_label_new("Upload Rate");
904  snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
905  value = gtk_label_new(buf);
906  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
907  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
908  name = gtk_label_new("ETA");
909  snprintf(buf, sizeof buf, "%i", sb[index].eta);
910  value = gtk_label_new(buf);
911  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
912  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
913  name = gtk_label_new("Total Peers");
914  snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
915  value = gtk_label_new(buf);
916  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
917  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
918  name = gtk_label_new("Uploading Peers");
919  snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
920  value = gtk_label_new(buf);
921  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
922  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
923  name = gtk_label_new("Downloading Peers");
924  snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
925  value = gtk_label_new(buf);
926  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
927  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
928  name = gtk_label_new("Downloaded");
929  snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
930  value = gtk_label_new(buf);
931  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
932  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
933  name = gtk_label_new("Uploaded");
934  snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
935  value = gtk_label_new(buf);
936  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
937  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
938
939  gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
940  g_signal_connect(G_OBJECT(wind), "response",
941                   G_CALLBACK(gtk_widget_destroy), NULL);
942  gtk_widget_show_all(wind);
943  free(sb);
944}
945
946gboolean
947savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat) {
948  char *errstr;
949  tr_stat_t *st;
950  gboolean ret;
951
952  assert(NULL != tr || 0 <= count);
953
954  if(0 <= count)
955    ret = cf_savestate(count, stat, &errstr);
956  else {
957    count = tr_torrentStat(tr, &st);
958    ret = cf_savestate(count, st, &errstr);
959    free(st);
960  }
961
962  if(!ret) {
963    errmsg(wind, "%s", errstr);
964    g_free(errstr);
965  }
966
967  return ret;
968}
Note: See TracBrowser for help on using the repository browser.