source: trunk/gtk/main.c @ 17

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

Update 2005-12-18

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