source: trunk/gtk/main.c @ 11

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

Update 2005-12-04

File size: 30.9 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);
83void
84fixbuttons(GtkTreeSelection *sel, gpointer gdata);
85void
86dfname(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
87       GtkTreeIter *iter, gpointer gdata);
88void
89dfprog(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
90       GtkTreeIter *iter, gpointer gdata);
91
92gboolean
93updatemodel(gpointer gdata);
94gboolean
95listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
96gboolean
97listpopup(GtkWidget *widget, gpointer gdata);
98void
99dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter);
100void
101actionclick(GtkWidget *widget, gpointer gdata);
102
103void
104makeaddwind(struct cbdata *data);
105gboolean
106addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
107           const char *dir, gboolean paused);
108void
109fileclick(GtkWidget *widget, gpointer gdata);
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(tr), &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
237  data->tr = tr;
238  data->wind = GTK_WINDOW(wind);
239  data->timer = -1;
240  /* filled in by makewind_list */
241  data->model = NULL;
242  data->view = NULL;
243  data->bar = GTK_STATUSBAR(status);
244  data->buttons = NULL;
245
246  gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
247
248  list = makewind_list(data);
249  gtk_widget_size_request(list, &req);
250  gtk_container_add(GTK_CONTAINER(scroll), list);
251  gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
252
253  gtk_statusbar_push(GTK_STATUSBAR(status), 0, "");
254  gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
255
256  gtk_container_add(GTK_CONTAINER(wind), vbox);
257  gtk_window_set_default_size(GTK_WINDOW(wind), req.width, req.height);
258  g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
259  gtk_widget_show_all(wind);
260
261  for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
262    ts = ii->data;
263    addtorrent(tr, GTK_WINDOW(wind),
264               ts->ts_torrent, ts->ts_directory, ts->ts_paused);
265    cf_freestate(ts);
266  }
267  g_list_free(saved);
268
269  data->timer = g_timeout_add(500, updatemodel, data);
270  updatemodel(data);
271}
272
273/* XXX is this the right thing to do? */
274#define TR_TORRENT_NEEDS_STOP(t) \
275  ((t) & TR_STATUS_CHECK || (t) & TR_STATUS_DOWNLOAD || (t) & TR_STATUS_SEED)
276
277gboolean
278winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
279  struct cbdata *data = gdata;
280  struct exitdata *edata;
281  tr_stat_t *st;
282  int ii;
283
284  if(0 >= data->timer)
285    g_source_remove(data->timer);
286  data->timer = -1;
287
288  for(ii = tr_torrentStat(data->tr, &st); 0 < ii; ii--) {
289    if(TR_TORRENT_NEEDS_STOP(st[ii-1].status)) {
290      fprintf(stderr, "quit: stopping %i %s\n", ii, st[ii-1].info.name);
291      tr_torrentStop(data->tr, ii - 1);
292    } else {
293      fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
294      tr_torrentClose(data->tr, ii - 1);
295    }
296  }
297  free(st);
298
299  /* XXX should disable widgets or something */
300
301  /* try to wait until torrents stop before exiting */
302  edata = g_new0(struct exitdata, 1);
303  edata->cbdata = data;
304  edata->started = time(NULL);
305  edata->timer = g_timeout_add(500, exitcheck, edata);
306
307  fprintf(stderr, "quit: starting timeout at %i\n", edata->started);
308
309  /* returning FALSE means to destroy the window */
310  return TRUE;
311}
312
313gboolean
314exitcheck(gpointer gdata) {
315  struct exitdata *data = gdata;
316  tr_stat_t *st;
317  int ii;
318
319  for(ii = tr_torrentStat(data->cbdata->tr, &st); 0 < ii; ii--) {
320    if(TR_STATUS_PAUSE == st[ii-1].status) {
321      fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
322      tr_torrentClose(data->cbdata->tr, ii - 1);
323    }
324  }
325  free(st);
326
327  fprintf(stderr, "quit: %i torrents left at %i\n",
328          tr_torrentCount(data->cbdata->tr), time(NULL));
329  /* keep going if we still have torrents and haven't hit the exit timeout */
330  if(0 < tr_torrentCount(data->cbdata->tr) &&
331     time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
332    updatemodel(data->cbdata);
333    return TRUE;
334  }
335
336  fprintf(stderr, "quit: giving up on %i torrents\n",
337          tr_torrentCount(data->cbdata->tr));
338
339  for(ii = tr_torrentCount(data->cbdata->tr); 0 < ii; ii--)
340    tr_torrentClose(data->cbdata->tr, ii - 1);
341
342  /* exit otherwise */
343
344  if(0 >= data->timer)
345    g_source_remove(data->timer);
346  data->timer = -1;
347
348  gtk_widget_destroy(GTK_WIDGET(data->cbdata->wind));
349  tr_close(data->cbdata->tr);
350  g_free(data->cbdata);
351  g_free(data);
352  gtk_main_quit();
353
354  return FALSE;
355}
356
357GtkWidget *
358makewind_toolbar(struct cbdata *data) {
359  GtkWidget *bar = gtk_toolbar_new();
360  GtkToolItem *item;
361  unsigned int ii;
362
363  gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
364  gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
365
366  data->buttons = g_new(GtkWidget*, ALEN(actionitems));
367
368  for(ii = 0; ii < ALEN(actionitems); ii++) {
369    item = gtk_tool_button_new_from_stock(actionitems[ii].id);
370    data->buttons[ii] = GTK_WIDGET(item);
371    gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), actionitems[ii].name);
372    gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
373                              actionitems[ii].ttext, actionitems[ii].tpriv);
374    g_object_set_data(G_OBJECT(item), LIST_ACTION,
375                      GINT_TO_POINTER(actionitems[ii].act));
376    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
377                      GINT_TO_POINTER(FROM_BUTTON));
378    g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
379    gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1);
380  }
381
382  return bar;
383}
384
385/* XXX check for unused data in model */
386enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
387      MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
388      MC_ROW_INDEX, MC_ROW_COUNT};
389
390GtkWidget *
391makewind_list(struct cbdata *data) {
392  GType types[] = {
393    /* info->name, info->totalSize, status,     error,         progress */
394    G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT,
395    /* rateDownload, rateUpload,   eta,        peersTotal, peersUploading */
396    G_TYPE_FLOAT,    G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
397    /* peersDownloading, pieces,         downloaded,    uploaded */
398    G_TYPE_INT,          TR_TYPE_PIECES, G_TYPE_UINT64, G_TYPE_UINT64,
399    /* index into the torrent array */
400    G_TYPE_INT};
401  GtkListStore *model;
402  GtkWidget *view;
403  GtkTreeViewColumn *col;
404  GtkTreeSelection *sel;
405  GtkCellRenderer *namerend, *progrend;
406
407  assert(MC_ROW_COUNT == ALEN(types));
408
409  model = gtk_list_store_newv(MC_ROW_COUNT, types);
410  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
411  /* XXX do I need to worry about reference counts anywhere else? */
412  g_object_unref(G_OBJECT(model));
413  data->model = model;
414  data->view = GTK_TREE_VIEW(view);
415
416  namerend = gtk_cell_renderer_text_new();
417  col = gtk_tree_view_column_new_with_attributes("Name", namerend, NULL);
418  gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
419  gtk_tree_view_column_set_expand(col, TRUE);
420  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
421
422  progrend = gtk_cell_renderer_torrent_new();
423  g_object_set(progrend, "label", "<big>  fnord    fnord  </big>", NULL);
424  col = gtk_tree_view_column_new_with_attributes("Progress", progrend, NULL);
425  gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
426  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
427
428  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
429  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
430  gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
431  g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
432  g_signal_connect(G_OBJECT(view), "button-press-event",
433                   G_CALLBACK(listclick), data);
434  g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
435  gtk_widget_show_all(view);
436
437  return view;
438}
439
440/* disable buttons the user shouldn't be able to click on */
441void
442fixbuttons(GtkTreeSelection *sel, gpointer gdata) {
443  struct cbdata *data = gdata;
444  GtkTreeModel *model;
445  GtkTreeIter iter;
446  gboolean selected;
447  unsigned int ii;
448  int status;
449
450  if(NULL == sel)
451    sel = gtk_tree_view_get_selection(data->view);
452  if((selected = gtk_tree_selection_get_selected(sel, &model, &iter)))
453    gtk_tree_model_get(model, &iter, MC_STAT, &status, -1);
454
455  for(ii = 0; ii < ALEN(actionitems); ii++)
456    if(actionitems[ii].avail)
457      gtk_widget_set_sensitive(data->buttons[ii],
458                               (selected && (actionitems[ii].avail & status)));
459}
460
461void
462dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
463       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
464  char *name, *mb, *err, *str, *top, *bottom;
465  guint64 size;
466  gfloat prog;
467  int status, eta, tpeers, upeers, dpeers;
468
469  /* XXX should I worry about gtk_tree_model_get failing? */
470  gtk_tree_model_get(model, iter, MC_NAME, &name, MC_STAT, &status,
471    MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta, MC_PEERS, &tpeers,
472    MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
473
474  if(0 > eta)
475    eta = 0;
476  if(0 > tpeers)
477    tpeers = 0;
478  if(0 > upeers)
479    upeers = 0;
480  if(0 > dpeers)
481    dpeers = 0;
482  mb = readablesize(size, 1);
483  prog *= 100;
484
485  if(status & TR_STATUS_CHECK)
486    top = g_strdup_printf("Checking existing files (%.1f%%)", prog);
487  else if(status & TR_STATUS_DOWNLOAD)
488    top = g_strdup_printf("Finishing in %02i:%02i:%02i (%.1f%%)",
489                           eta / 60 / 60, eta / 60 % 60, eta % 60, prog);
490  else if(status & TR_STATUS_SEED)
491    top = g_strdup_printf("Seeding, uploading to %d of %d peer%s",
492                           dpeers, tpeers, (1 == tpeers ? "" : "s"));
493  else if(status & TR_STATUS_STOPPING)
494    top = g_strdup("Stopping...");
495  else if(status & TR_STATUS_PAUSE)
496    top = g_strdup_printf("Paused (%.1f%%)", prog);
497  else {
498    top = g_strdup("");
499    assert("XXX unknown status");
500  }
501
502  if(status & TR_TRACKER_ERROR) {
503    gtk_tree_model_get(model, iter, MC_ERR, &err, -1);
504    bottom = g_strconcat("Error: ", err, NULL);
505    g_free(err);
506  }
507  else if(status & TR_STATUS_DOWNLOAD)
508    bottom = g_strdup_printf("Downloading from %i of %i peer%s",
509                             upeers, tpeers, (1 == tpeers ? "" : "s"));
510  else
511    bottom = NULL;
512
513  str = g_markup_printf_escaped("<big>%s (%s)</big>\n<small>%s\n%s</small>",
514                                name, mb, top, (NULL == bottom ? "" : bottom));
515  g_object_set(rend, "markup", str, NULL);
516  g_free(name);
517  g_free(mb);
518  g_free(str);
519  g_free(top);
520  g_free(bottom);
521}
522
523void
524dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
525       GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
526  char *dlstr, *ulstr, *str;
527  gfloat prog, dl, ul;
528
529  /* XXX should I worry about gtk_tree_model_get failing? */
530  gtk_tree_model_get(model, iter, MC_PROG, &prog,
531                     MC_DRATE, &dl, MC_URATE, &ul, -1);
532  if(0.0 > prog)
533    prog = 0.0;
534  else if(1.0 < prog)
535    prog = 1.0;
536
537  dlstr = readablesize(dl * 1024.0, 2);
538  ulstr = readablesize(ul * 1024.0, 2);
539  str = g_strdup_printf("<small>DL: %s/s\nUL: %s/s</small>", dlstr, ulstr);
540  g_object_set(rend, "text", str, "value", prog, NULL);
541  g_free(dlstr);
542  g_free(ulstr);
543  g_free(str);
544}
545
546gboolean
547updatemodel(gpointer gdata) {
548  struct cbdata *data = gdata;
549  tr_stat_t *st;
550  int ii, max;
551  GtkTreeIter iter;
552  float up, down;
553  char *upstr, *downstr, *str;
554
555  max = tr_torrentStat(data->tr, &st);
556  for(ii = 0; ii < max; ii++) {
557    if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
558         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
559      gtk_list_store_append(data->model, &iter);
560    /* XXX find out if setting the same data emits changed signal */
561    gtk_list_store_set(
562      data->model, &iter, MC_ROW_INDEX, ii,
563      MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize,
564      MC_STAT, st[ii].status, MC_ERR, st[ii].error, MC_PROG, st[ii].progress,
565      MC_DRATE, st[ii].rateDownload, MC_URATE, st[ii].rateUpload,
566      MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
567      MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
568      MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
569  }
570  free(st);
571
572  /* remove any excess rows */
573  if(gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter))
574    while(gtk_list_store_remove(data->model, &iter))
575      ;
576
577  /* update the status bar */
578  tr_torrentRates(data->tr, &up, &down);
579  downstr = readablesize(down * 1024.0, 2);
580  upstr = readablesize(up * 1024.0, 2);
581  str = g_strdup_printf("     Total DL: %s/s     Total UL: %s/s", upstr, downstr);
582  gtk_statusbar_pop(data->bar, 0);
583  gtk_statusbar_push(data->bar, 0, str);
584  g_free(str);
585  g_free(upstr);
586  g_free(downstr);
587
588  /* the status of the selected item may have changed, so update the buttons */
589  fixbuttons(NULL, data);
590
591  return TRUE;
592}
593
594gboolean
595listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
596  struct cbdata *data = gdata;
597  GtkTreePath *path;
598  GtkTreeIter iter;
599
600  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
601    if(!gtk_tree_view_get_path_at_pos(data->view, event->x, event->y, &path,
602                                      NULL, NULL, NULL))
603      dopopupmenu(event, data, NULL);
604    else {
605      if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
606        dopopupmenu(event, data, &iter);
607      gtk_tree_path_free(path);
608    }
609    return TRUE;
610  }
611
612  return FALSE;
613}
614
615gboolean
616listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
617  struct cbdata *data = gdata;
618  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
619  GtkTreeModel *model;
620  GtkTreeIter iter;
621
622  if(!gtk_tree_selection_get_selected(sel, &model, &iter))
623    dopopupmenu(NULL, data, NULL);
624  else {
625    assert(model == GTK_TREE_MODEL(data->model));
626    dopopupmenu(NULL, data, &iter);
627  }
628
629  return TRUE;
630}
631
632void
633dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter) {
634  GtkWidget *menu = gtk_menu_new();
635  GtkWidget *item;
636  unsigned int ii;
637  int index, status;
638
639  index = -1;
640  if(NULL != iter)
641    gtk_tree_model_get(GTK_TREE_MODEL(data->model), iter,
642                       MC_ROW_INDEX, &index, MC_STAT, &status, -1);
643
644  /* XXX am I leaking references here? */
645  for(ii = 0; ii < ALEN(actionitems); ii++) {
646    if(actionitems[ii].nomenu ||
647       (actionitems[ii].avail &&
648        (0 > index || !(actionitems[ii].avail & status))))
649      continue;
650    item = gtk_menu_item_new_with_label(actionitems[ii].name);
651    g_object_set_data(G_OBJECT(item), LIST_ACTION,
652                      GINT_TO_POINTER(actionitems[ii].act));
653    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
654                      GINT_TO_POINTER(FROM_POPUP));
655    g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
656    g_signal_connect(G_OBJECT(item), "activate",
657                     G_CALLBACK(actionclick), data);
658    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
659  }
660
661  gtk_widget_show_all(menu);
662
663  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
664                 (NULL == event ? 0 : event->button),
665                 gdk_event_get_time((GdkEvent*)event));
666}
667
668void
669actionclick(GtkWidget *widget, gpointer gdata) {
670  struct cbdata *data = gdata;
671  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
672  GtkTreeModel *model;
673  GtkTreeIter iter;
674  enum listact act =
675    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
676  enum listfrom from =
677    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
678  int index;
679  tr_stat_t *sb;
680
681  switch(act) {
682    case ACT_OPEN:
683      makeaddwind(data);
684      return;
685    case ACT_PREF:
686      makeprefwindow(data->wind, data->tr);
687      return;
688    default:
689      break;
690  }
691
692  index = -1;
693  switch(from) {
694    case FROM_BUTTON:
695      if(gtk_tree_selection_get_selected(sel, &model, &iter))
696        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
697                           MC_ROW_INDEX, &index, -1);
698      /* XXX should I assert(0 <= index) to insure a row was selected? */
699      break;
700    case FROM_POPUP:
701      index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
702      break;
703    default:
704      assert(!"unknown action source");
705      break;
706  }
707
708  if(0 <= index) {
709    switch(act) {
710      case ACT_START:
711        tr_torrentStart(data->tr, index);
712        savetorrents(data->tr, data->wind, -1, NULL);
713        updatemodel(data);
714        break;
715      case ACT_STOP:
716        tr_torrentStop(data->tr, index);
717        savetorrents(data->tr, data->wind, -1, NULL);
718        updatemodel(data);
719        break;
720      case ACT_DELETE:
721        /* XXX need to be able to stat just one torrent */
722        if(index >= tr_torrentStat(data->tr, &sb)) {
723          assert(!"XXX i'm tired");
724        }
725        if(TR_TORRENT_NEEDS_STOP(sb[index].status))
726          tr_torrentStop(data->tr, index);
727        free(sb);
728        tr_torrentClose(data->tr, index);
729        savetorrents(data->tr, data->wind, -1, NULL);
730        updatemodel(data);
731        break;
732      case ACT_INFO:
733        makeinfowind(data, index);
734        break;
735      default:
736        assert(!"unknown type");
737        break;
738    }
739  }
740}
741
742void
743makeaddwind(struct cbdata *data) {
744  GtkWidget *wind = gtk_file_selection_new("Add a Torrent");
745
746  g_object_set_data(G_OBJECT(GTK_FILE_SELECTION(wind)->ok_button),
747                    CBDATA_PTR, data);
748  g_signal_connect(GTK_FILE_SELECTION(wind)->ok_button, "clicked",
749                   G_CALLBACK(fileclick), wind);
750  g_signal_connect_swapped(GTK_FILE_SELECTION(wind)->cancel_button, "clicked",
751                           G_CALLBACK(gtk_widget_destroy), wind); 
752  gtk_window_set_transient_for(GTK_WINDOW(wind), data->wind);
753  gtk_window_set_destroy_with_parent(GTK_WINDOW(wind), TRUE);
754  gtk_window_set_modal(GTK_WINDOW(wind), TRUE);
755  gtk_widget_show_all(wind);
756}
757
758gboolean
759addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
760           const char *dir, gboolean paused) {
761  char *wd;
762
763  if(NULL == dir) {
764    dir = cf_getpref(PREF_DIR);
765    if(!mkdir_p(dir, 0777)) {
766      errmsg(parentwind, "Failed to create download directory %s:\n%s",
767             dir, strerror(errno));
768      return FALSE;
769    }
770  }
771
772  if(0 != tr_torrentInit(tr, torrent)) {
773    /* XXX would be nice to have errno strings, are they printed to stdout? */
774    errmsg(parentwind, "Failed to open torrent file %s", torrent);
775    return FALSE;
776  }
777
778  if(NULL != dir)
779    tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
780  else {
781    wd = g_new(char, MAXPATHLEN + 1);
782    if(NULL == getcwd(wd, MAXPATHLEN + 1))
783      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
784    else {
785      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
786      free(wd);
787    }
788  }
789
790  if(!paused)
791    tr_torrentStart(tr, tr_torrentCount(tr) - 1);
792  return TRUE;
793}
794
795void
796fileclick(GtkWidget *widget, gpointer gdata) {
797  struct cbdata *data = g_object_get_data(G_OBJECT(widget), CBDATA_PTR);
798  GtkWidget *wind = gdata;
799  const char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(wind));
800
801  if(addtorrent(data->tr, data->wind, file, NULL, FALSE)) {
802    updatemodel(data);
803    savetorrents(data->tr, data->wind, -1, NULL);
804  }
805
806  gtk_widget_destroy(wind);
807}
808
809const char *
810statusstr(int status) {
811  switch(status) {
812    case TR_STATUS_CHECK:       return "check";
813    case TR_STATUS_DOWNLOAD:    return "download";
814    case TR_STATUS_SEED:        return "seed";
815    case TR_STATUS_STOPPING:    return "stopping";
816    case TR_STATUS_STOPPED:     return "stopped";
817    case TR_STATUS_PAUSE:       return "pause";
818    case TR_TRACKER_ERROR:      return "error";
819    default:
820      assert(!"unknown status code");
821      return NULL;
822  }
823}
824
825void
826makeinfowind(struct cbdata *data, int index) {
827  tr_stat_t *sb;
828  GtkWidget *wind, *table, *name, *value;
829  char buf[32];
830
831  if(index >= tr_torrentStat(data->tr, &sb)) {
832    assert(!"XXX i'm tired");
833  }
834  wind = gtk_dialog_new_with_buttons(sb[index].info.name, data->wind,
835    GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
836
837  table = gtk_table_new(21, 2, FALSE);
838
839  name = gtk_label_new("Torrent File");
840  value = gtk_label_new(sb[index].info.torrent);
841  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
842  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
843  name = gtk_label_new("Name");
844  value = gtk_label_new(sb[index].info.name);
845  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
846  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
847  name = gtk_label_new("Tracker Address");
848  value = gtk_label_new(sb[index].info.trackerAddress);
849  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
850  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
851  name = gtk_label_new("Tracker Port");
852  snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
853  value = gtk_label_new(buf);
854  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
855  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
856  name = gtk_label_new("Tracker Announce URL");
857  value = gtk_label_new(sb[index].info.trackerAnnounce);
858  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
859  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
860  name = gtk_label_new("Piece Size");
861  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
862  value = gtk_label_new(buf);
863  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
864  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
865  name = gtk_label_new("Piece Count");
866  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
867  value = gtk_label_new(buf);
868  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
869  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
870  name = gtk_label_new("Total Size");
871  snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
872  value = gtk_label_new(buf);
873  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
874  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
875  name = gtk_label_new("File Count");
876  snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
877  value = gtk_label_new(buf);
878  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
879  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
880  name = gtk_label_new("Status");
881  value = gtk_label_new(statusstr(sb[index].status));
882  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
883  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
884  name = gtk_label_new("Error");
885  value = gtk_label_new(sb[index].error);
886  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
887  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
888  name = gtk_label_new("Progress");
889  snprintf(buf, sizeof buf, "%f", sb[index].progress);
890  value = gtk_label_new(buf);
891  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
892  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
893  name = gtk_label_new("Download Rate");
894  value = gtk_label_new(buf);
895  snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
896  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
897  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
898  name = gtk_label_new("Upload Rate");
899  snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
900  value = gtk_label_new(buf);
901  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
902  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
903  name = gtk_label_new("ETA");
904  snprintf(buf, sizeof buf, "%i", sb[index].eta);
905  value = gtk_label_new(buf);
906  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
907  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
908  name = gtk_label_new("Total Peers");
909  snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
910  value = gtk_label_new(buf);
911  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
912  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
913  name = gtk_label_new("Uploading Peers");
914  snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
915  value = gtk_label_new(buf);
916  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
917  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
918  name = gtk_label_new("Downloading Peers");
919  snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
920  value = gtk_label_new(buf);
921  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
922  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
923  name = gtk_label_new("Downloaded");
924  snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
925  value = gtk_label_new(buf);
926  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
927  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
928  name = gtk_label_new("Uploaded");
929  snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
930  value = gtk_label_new(buf);
931  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
932  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
933
934  gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
935  g_signal_connect(G_OBJECT(wind), "response",
936                   G_CALLBACK(gtk_widget_destroy), NULL);
937  gtk_widget_show_all(wind);
938  free(sb);
939}
940
941gboolean
942savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat) {
943  char *errstr;
944  tr_stat_t *st;
945  gboolean ret;
946
947  assert(NULL != tr || 0 <= count);
948
949  if(0 <= count)
950    ret = cf_savestate(count, stat, &errstr);
951  else {
952    count = tr_torrentStat(tr, &st);
953    ret = cf_savestate(count, st, &errstr);
954    free(st);
955  }
956
957  if(!ret) {
958    errmsg(wind, "%s", errstr);
959    g_free(errstr);
960  }
961
962  return ret;
963}
Note: See TracBrowser for help on using the repository browser.