source: trunk/gtk/main.c @ 5

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

Update 2005-11-18

File size: 29.1 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 <assert.h>
28#include <string.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <sys/param.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <gtk/gtk.h>
36
37#include "conf.h"
38#include "transmission.h"
39#include "util.h"
40
41#define TRACKER_EXIT_TIMEOUT    30
42
43struct cbdata {
44  tr_handle_t *tr;
45  GtkWidget *wind;
46  GtkListStore *model;
47  GtkTreeView *view;
48  guint timer;
49};
50
51struct exitdata {
52  struct cbdata *cbdata;
53  time_t started;
54  guint timer;
55};
56
57struct pieces {
58  char p[120];
59};
60
61void
62readargs(int argc, char **argv, int *port, int *limit);
63void
64maketypes(void);
65gpointer
66tr_pieces_copy(gpointer);
67void
68tr_pieces_free(gpointer);
69
70void
71makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
72gboolean
73winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
74gboolean
75exitcheck(gpointer gdata);
76GtkWidget *
77makewind_toolbar(struct cbdata *data);
78GtkWidget *
79makewind_list(struct cbdata *data);
80
81gboolean
82updatemodel(gpointer gdata);
83gboolean
84listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
85gboolean
86listpopup(GtkWidget *widget, gpointer userdata);
87void
88dopopupmenu(GtkWidget *widget, GdkEventButton *event, struct cbdata *data);
89void
90actionclick(GtkWidget *widget, gpointer gdata);
91
92void
93makeaddwind(struct cbdata *data);
94gboolean
95addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
96           const char *dir, gboolean paused);
97void
98fileclick(GtkWidget *widget, gpointer gdata);
99const char *
100statusstr(int status);
101void
102makeinfowind(struct cbdata *data, int index);
103gboolean
104savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat);
105
106#define TR_TYPE_PIECES_NAME     "tr-type-pieces"
107#define TR_TYPE_PIECES          ((const GType)tr_type_pieces)
108#define TR_PIECES(ptr)          ((struct pieces*)ptr)
109GType tr_type_pieces;
110
111#define LIST_ACTION           "torrent-list-action"
112enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO };
113#define LIST_ACTION_FROM      "torrent-list-action-from"
114enum listfrom { FROM_BUTTON, FROM_POPUP };
115
116#define LIST_INDEX            "torrent-list-index"
117
118struct { gint pos; const gchar *name; const gchar *id; enum listact act;
119  const char *ttext; const char *tpriv; }
120actionitems[] = {
121  {0,  "Add",         GTK_STOCK_ADD,          ACT_OPEN,
122   "Add a new torrent file", "XXX"},
123  {1,  "Resume",      GTK_STOCK_MEDIA_PLAY,   ACT_START,
124   "Resume a torrent that has been paused", "XXX"},
125  {2,  "Pause",       GTK_STOCK_MEDIA_PAUSE,  ACT_STOP,
126   "Pause a torrent", "XXX"},
127  {3,  "Remove",      GTK_STOCK_REMOVE,       ACT_DELETE,
128   "Remove a torrent from the list", "XXX"},
129  {4,  "Properties",  GTK_STOCK_PROPERTIES,   ACT_INFO,
130   "Get additional information for a torrent", "XXX"},
131};
132
133#define CBDATA_PTR              "callback-data-pointer"
134int
135main(int argc, char **argv) {
136  GtkWidget *mainwind, *preferr, *stateerr;
137  char *err;
138  int port, limit;
139  tr_handle_t *tr;
140  GList *saved;
141
142  gtk_init(&argc, &argv);
143  readargs(argc, argv, &port, &limit);
144
145  tr = tr_init();
146
147  if(cf_init(tr_getPrefsDirectory(tr), &err)) {
148    if(cf_lock(&err)) {
149      /* create main window now so any error dialogs can be it's children */
150      mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
151      preferr = NULL;
152      stateerr = NULL;
153
154      if(!cf_loadprefs(&err)) {
155        preferr = errmsg(mainwind, "%s", err);
156        g_free(err);
157      }
158      saved = cf_loadstate(&err);
159      if(NULL != err) {
160        stateerr = errmsg(mainwind, "%s", err);
161        g_free(err);
162      }
163
164      /* XXX need to remove port and limit options and make them prefs */
165      /* XXX need prefs gui */
166      /* XXX need default save dir pref */
167
168      if(0 != port)
169        tr_setBindPort(tr, port);
170      if(0 != limit)
171        tr_setUploadLimit(tr, limit);
172
173      maketypes();
174      makewind(mainwind, tr, saved);
175
176      if(NULL != preferr)
177        gtk_widget_show_all(preferr);
178      if(NULL != stateerr)
179        gtk_widget_show_all(stateerr);
180    } else {
181      errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
182      g_free(err);
183    }
184  } else {
185    errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
186    g_free(err);
187  }
188
189  gtk_main();
190
191  return 0;
192}
193
194void
195readargs(int argc, char **argv, int *port, int *limit) {
196  char *name;
197  int opt, num;
198
199  *port = 0;
200  *limit = 0;
201
202  if(NULL == (name = strrchr(argv[0], '/')) || '\0' == *(++name))
203    name = argv[0];
204
205  for(num = 1; num < argc; num++)
206    if(0 == strcmp(argv[num], "-help") || 0 == strcmp(argv[num], "--help"))
207      goto usage;
208
209  while(0 <= (opt = getopt(argc, argv, "hp:u:"))) {
210    switch(opt) {
211      case 'p':
212        num = atoi(optarg);
213        if(0 < num && 0xffff > num)
214          *port = num;
215        break;
216      case 'u':
217        num = atoi(optarg);
218        if(0 != num)
219          *limit = num;
220        break;
221      default:
222        goto usage;
223    }
224  }
225
226  return;
227
228 usage:
229  printf("usage: %s [-h] [-p port] [-u limit]\n", name);
230  exit(1);
231}
232
233void
234maketypes(void) {
235  tr_type_pieces = g_boxed_type_register_static(
236    TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
237}
238
239gpointer
240tr_pieces_copy(gpointer data) {
241  return g_memdup(data, sizeof(struct pieces));
242}
243
244void
245tr_pieces_free(gpointer data) {
246  g_free(data);
247}
248
249void
250makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
251  GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
252  GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
253  struct cbdata *data = g_new0(struct cbdata, 1);
254  GtkWidget *list;
255  GtkRequisition req;
256  GList *ii;
257  struct cf_torrentstate *ts;
258
259  data->tr = tr;
260  data->wind = wind;
261  data->timer = -1;
262  /* filled in by makewind_list */
263  data->model = NULL;
264  data->view = NULL;
265
266  gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
267
268  list = makewind_list(data);
269  gtk_widget_size_request(list, &req);
270  gtk_container_add(GTK_CONTAINER(scroll), list);
271  gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 5);
272
273  gtk_container_add(GTK_CONTAINER(wind), vbox);
274  gtk_window_set_default_size(GTK_WINDOW(wind), req.width, req.height);
275  g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
276  gtk_widget_show_all(wind);
277
278  for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
279    ts = ii->data;
280    addtorrent(tr, wind, ts->ts_torrent, ts->ts_directory, ts->ts_paused);
281    cf_freestate(ts);
282  }
283  g_list_free(saved);
284
285  data->timer = g_timeout_add(500, updatemodel, data);
286  updatemodel(data);
287}
288
289/* XXX is this the right thing to do? */
290#define TR_TORRENT_NEEDS_STOP(t) \
291  ((t) & TR_STATUS_CHECK || (t) & TR_STATUS_DOWNLOAD || (t) & TR_STATUS_SEED)
292
293gboolean
294winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
295  struct cbdata *data = gdata;
296  struct exitdata *edata;
297  tr_stat_t *st;
298  int ii;
299
300  if(0 >= data->timer)
301    g_source_remove(data->timer);
302  data->timer = -1;
303
304  for(ii = tr_torrentStat(data->tr, &st); 0 < ii; ii--) {
305    if(TR_TORRENT_NEEDS_STOP(st[ii-1].status)) {
306      fprintf(stderr, "quit: stopping %i %s\n", ii, st[ii-1].info.name);
307      tr_torrentStop(data->tr, ii - 1);
308    } else {
309      fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
310      tr_torrentClose(data->tr, ii - 1);
311    }
312  }
313  free(st);
314
315  /* XXX should disable widgets or something */
316
317  /* try to wait until torrents stop before exiting */
318  edata = g_new0(struct exitdata, 1);
319  edata->cbdata = data;
320  edata->started = time(NULL);
321  edata->timer = g_timeout_add(500, exitcheck, edata);
322
323  fprintf(stderr, "quit: starting timeout at %i\n", edata->started);
324
325  //exitcheck(edata);
326
327  /* returning FALSE means to destroy the window */
328  return TRUE;
329}
330
331gboolean
332exitcheck(gpointer gdata) {
333  struct exitdata *data = gdata;
334  tr_stat_t *st;
335  int ii;
336
337  for(ii = tr_torrentStat(data->cbdata->tr, &st); 0 < ii; ii--) {
338    if(TR_STATUS_PAUSE == st[ii-1].status) {
339      fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
340      tr_torrentClose(data->cbdata->tr, ii - 1);
341    }
342  }
343  free(st);
344
345  fprintf(stderr, "quit: %i torrents left at %i\n",
346          tr_torrentCount(data->cbdata->tr), time(NULL));
347  /* keep going if we still have torrents and haven't hit the exit timeout */
348  if(0 < tr_torrentCount(data->cbdata->tr) &&
349     time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
350    updatemodel(data->cbdata);
351    return TRUE;
352  }
353
354  fprintf(stderr, "quit: giving up on %i torrents\n",
355          tr_torrentCount(data->cbdata->tr));
356
357  for(ii = tr_torrentCount(data->cbdata->tr); 0 < ii; ii--)
358    tr_torrentClose(data->cbdata->tr, ii - 1);
359
360  /* exit otherwise */
361
362  if(0 >= data->timer)
363    g_source_remove(data->timer);
364  data->timer = -1;
365
366  gtk_widget_destroy(data->cbdata->wind);
367  tr_close(data->cbdata->tr);
368  g_free(data->cbdata);
369  g_free(data);
370  gtk_main_quit();
371
372  return FALSE;
373}
374
375GtkWidget *
376makewind_toolbar(struct cbdata *data) {
377  GtkWidget *bar = gtk_toolbar_new();
378  GtkToolItem *item;
379  unsigned int ii;
380
381  gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
382  gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
383
384  for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
385    item = gtk_tool_button_new_from_stock(actionitems[ii].id);
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), GTK_TOOL_ITEM(item), actionitems[ii].pos);
395  }
396
397  return bar;
398}
399
400enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
401      MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
402      MC_ROW_INDEX, MC_ROW_COUNT};
403
404GtkWidget *
405makewind_list(struct cbdata *data) {
406  GType types[] = {
407    /* info->name, info->totalSize, status,     error,         progress */
408    G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT,
409    /* rateDownload, rateUpload,   eta,        peersTotal, peersUploading */
410    G_TYPE_FLOAT,    G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
411    /* peersDownloading, pieces,         downloaded,    uploaded */
412    G_TYPE_INT,          TR_TYPE_PIECES, G_TYPE_UINT64, G_TYPE_UINT64,
413    /* index into the torrent array */
414    G_TYPE_INT};
415  GtkListStore *model;
416  GtkWidget *view;
417  /*GtkTreeViewColumn *col;*/
418  GtkTreeSelection *sel;
419  GtkCellRenderer *rend;
420  GtkCellRenderer *rendprog;
421
422  assert(MC_ROW_COUNT == sizeof(types) / sizeof(types[0]));
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  rend = gtk_cell_renderer_text_new();
432  rendprog = gtk_cell_renderer_progress_new();
433  g_object_set(rendprog, "text", "", NULL);
434
435/*
436  col = gtk_tree_view_column_new_with_attributes(
437    "Name", rend, "text", MC_NAME, NULL);
438  gtk_tree_view_column_add_attribute(col, rend, "text", MC_SIZE);
439  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
440
441  col = gtk_tree_view_column_new_with_attributes(
442    "Status", rend, "text", MC_STAT, NULL);
443  gtk_tree_view_column_add_attribute(col, rend, "text", MC_ERR);
444  gtk_tree_view_column_add_attribute(col, rend, "text", MC_DPEERS);
445  gtk_tree_view_column_add_attribute(col, rend, "text", MC_UPEERS);
446  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
447
448  col = gtk_tree_view_column_new_with_attributes(
449    "Progress", rendprog, "value", MC_PROG, NULL);
450  gtk_tree_view_column_pack_start(col, rend, TRUE);
451  gtk_tree_view_column_add_attribute(col, rend, "text", MC_ETA);
452  gtk_tree_view_column_add_attribute(col, rend, "text", MC_DRATE);
453  gtk_tree_view_column_add_attribute(col, rend, "text", MC_URATE);
454  gtk_tree_view_column_add_attribute(col, rend, "text", MC_DOWN);
455  gtk_tree_view_column_add_attribute(col, rend, "text", MC_UP);
456  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
457*/
458
459  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
460    gtk_tree_view_column_new_with_attributes("Name", rend,
461                                             "text", MC_NAME, NULL));
462  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
463    gtk_tree_view_column_new_with_attributes("Size", rend,
464                                             "text", MC_SIZE, NULL));
465  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
466    gtk_tree_view_column_new_with_attributes("Status", rend,
467                                             "text", MC_STAT, NULL));
468  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
469    gtk_tree_view_column_new_with_attributes("Error", rend,
470                                             "text", MC_ERR, NULL));
471  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
472    gtk_tree_view_column_new_with_attributes("Progress", rendprog,
473                                             "value", MC_PROG, NULL));
474  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
475    gtk_tree_view_column_new_with_attributes("Download Rate", rend,
476                                             "text", MC_DRATE, NULL));
477  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
478    gtk_tree_view_column_new_with_attributes("Upload Rate", rend,
479                                             "text", MC_URATE, NULL));
480  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
481    gtk_tree_view_column_new_with_attributes("ETA", rend,
482                                             "text", MC_ETA, NULL));
483  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
484    gtk_tree_view_column_new_with_attributes("Peers", rend,
485                                             "text", MC_PEERS, NULL));
486  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
487    gtk_tree_view_column_new_with_attributes("Seeders", rend,
488                                             "text", MC_UPEERS, NULL));
489  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
490    gtk_tree_view_column_new_with_attributes("Leechers", rend,
491                                             "text", MC_DPEERS, NULL));
492  /*gtk_tree_view_append_column(GTK_TREE_VIEW(view),
493    gtk_tree_view_column_new_with_attributes("", rend,
494                                             "text", MC_PIECES, NULL));*/
495  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
496    gtk_tree_view_column_new_with_attributes("Downloaded", rend,
497                                             "text", MC_DOWN, NULL));
498  gtk_tree_view_append_column(GTK_TREE_VIEW(view),
499    gtk_tree_view_column_new_with_attributes("Uploaded", rend,
500                                             "text", MC_UP, NULL));
501
502  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
503  sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
504  gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
505  g_signal_connect(G_OBJECT(view), "button-press-event",
506                   G_CALLBACK(listclick), data);
507  g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
508  gtk_widget_show_all(view);
509
510  return view;
511}
512
513gboolean
514updatemodel(gpointer gdata) {
515  struct cbdata *data = gdata;
516  tr_stat_t *st;
517  int ii, max, prog;
518  GtkTreeIter iter;
519
520  max = tr_torrentStat(data->tr, &st);
521  for(ii = 0; ii < max; ii++) {
522    if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
523         gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
524      gtk_list_store_append(data->model, &iter);
525    if(0.0 > (prog = st[ii].progress * 100.0))
526      prog = 0;
527    else if(100 < prog)
528      prog = 100;
529    /* XXX find out if setting the same data emits changed signal */
530    gtk_list_store_set(data->model, &iter, MC_ROW_INDEX, ii,
531      MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize, MC_STAT, st[ii].status,
532      MC_ERR, st[ii].error, MC_PROG, prog, MC_DRATE, st[ii].rateDownload,
533      MC_URATE, st[ii].rateUpload, MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
534      MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
535      /*MC_PIECES, st[ii].pieces,*/ MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
536  }
537  free(st);
538
539  return TRUE;
540}
541
542gboolean
543listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
544  struct cbdata *data = gdata;
545
546  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
547    dopopupmenu(widget, event, data);
548    return TRUE;
549  }
550
551  return FALSE;
552}
553
554gboolean
555listpopup(GtkWidget *widget, gpointer userdata) {
556  dopopupmenu(widget, NULL, userdata);
557
558  return TRUE;
559}
560
561void
562dopopupmenu(GtkWidget *widget SHUTUP, GdkEventButton *event,
563            struct cbdata *data) {
564  GtkWidget *menu = gtk_menu_new();
565  GtkWidget *item;
566  GtkTreePath *path;
567  GtkTreeIter iter;
568  unsigned int ii;
569  int index;
570
571  index = -1;
572  if(NULL != event && gtk_tree_view_get_path_at_pos(
573       data->view, event->x, event->y, &path, NULL, NULL, NULL)) {
574    if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
575      gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter, MC_ROW_INDEX, &index, -1);
576    gtk_tree_path_free(path);
577  }
578
579  /* XXX am I leaking references here? */
580  /* XXX can I cache this in cbdata? */
581  for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
582    item = gtk_menu_item_new_with_label(actionitems[ii].name);
583    g_object_set_data(G_OBJECT(item), LIST_ACTION,
584                      GINT_TO_POINTER(actionitems[ii].act));
585    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
586                      GINT_TO_POINTER(FROM_POPUP));
587    g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
588    g_signal_connect(G_OBJECT(item), "activate",
589                     G_CALLBACK(actionclick), data);
590    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
591  }
592
593  gtk_widget_show_all(menu);
594
595  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
596                 (NULL == event ? 0 : event->button),
597                 gdk_event_get_time((GdkEvent*)event));
598}
599
600void
601actionclick(GtkWidget *widget, gpointer gdata) {
602  struct cbdata *data = gdata;
603  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
604  GtkTreeModel *model;
605  GtkTreeIter iter;
606  enum listact act =
607    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
608  enum listfrom from =
609    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
610  int index;
611  tr_stat_t *sb;
612
613  if(ACT_OPEN == act) {
614    makeaddwind(data);
615    return;
616  }
617
618  index = -1;
619  switch(from) {
620    case FROM_BUTTON:
621      if(gtk_tree_selection_get_selected(sel, &model, &iter))
622        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
623                           MC_ROW_INDEX, &index, -1);
624      /* XXX should I assert(0 <= index) to insure a row was selected? */
625      break;
626    case FROM_POPUP:
627      index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
628      break;
629    default:
630      assert(!"unknown action source");
631      break;
632  }
633
634  if(0 <= index) {
635    switch(act) {
636      case ACT_START:
637        tr_torrentStart(data->tr, index);
638        savetorrents(data->tr, data->wind, -1, NULL);
639        break;
640      case ACT_STOP:
641        tr_torrentStop(data->tr, index);
642        savetorrents(data->tr, data->wind, -1, NULL);
643        break;
644      case ACT_DELETE:
645        /* XXX need to be able to stat just one torrent */
646        if(index >= tr_torrentStat(data->tr, &sb)) {
647          assert(!"XXX i'm tired");
648        }
649        if(TR_TORRENT_NEEDS_STOP(sb[index].status))
650          tr_torrentStop(data->tr, index);
651        free(sb);
652        tr_torrentClose(data->tr, index);
653        gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
654        savetorrents(data->tr, data->wind, -1, NULL);
655        break;
656      case ACT_INFO:
657        makeinfowind(data, index);
658        break;
659      default:
660        assert(!"unknown type");
661        break;
662    }
663  }
664}
665
666void
667makeaddwind(struct cbdata *data) {
668  GtkWidget *wind = gtk_file_selection_new("Add a Torrent");
669
670  g_object_set_data(G_OBJECT(GTK_FILE_SELECTION(wind)->ok_button),
671                    CBDATA_PTR, data);
672  g_signal_connect(GTK_FILE_SELECTION(wind)->ok_button, "clicked",
673                   G_CALLBACK(fileclick), wind);
674  g_signal_connect_swapped(GTK_FILE_SELECTION(wind)->cancel_button, "clicked",
675                           G_CALLBACK(gtk_widget_destroy), wind); 
676  gtk_window_set_transient_for(GTK_WINDOW(wind), GTK_WINDOW(data->wind));
677  gtk_window_set_destroy_with_parent(GTK_WINDOW(wind), TRUE);
678  gtk_window_set_modal(GTK_WINDOW(wind), TRUE);
679  gtk_widget_show_all(wind);
680}
681
682gboolean
683addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
684           const char *dir, gboolean paused) {
685  char *wd;
686
687  if(0 != tr_torrentInit(tr, torrent)) {
688    /* XXX would be nice to have errno strings, are they printed to stdout? */
689    errmsg(parentwind, "Failed to open torrent file %s", torrent);
690    return FALSE;
691  }
692
693  if(NULL != dir)
694    tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
695  else {
696    /* XXX need pref for download directory */
697    wd = g_new(char, MAXPATHLEN + 1);
698    if(NULL == getcwd(wd, MAXPATHLEN + 1))
699      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
700    else {
701      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
702      free(wd);
703    }
704  }
705
706  if(!paused)
707    tr_torrentStart(tr, tr_torrentCount(tr) - 1);
708  return TRUE;
709}
710
711void
712fileclick(GtkWidget *widget, gpointer gdata) {
713  struct cbdata *data = g_object_get_data(G_OBJECT(widget), CBDATA_PTR);
714  GtkWidget *wind = gdata;
715  const char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(wind));
716
717  if(addtorrent(data->tr, data->wind, file, NULL, FALSE)) {
718    updatemodel(data);
719    savetorrents(data->tr, data->wind, -1, NULL);
720  }
721
722  gtk_widget_destroy(wind);
723}
724
725const char *
726statusstr(int status) {
727  switch(status) {
728    case TR_STATUS_CHECK:       return "check";
729    case TR_STATUS_DOWNLOAD:    return "download";
730    case TR_STATUS_SEED:        return "seed";
731    case TR_STATUS_STOPPING:    return "stopping";
732    case TR_STATUS_STOPPED:     return "stopped";
733    case TR_STATUS_PAUSE:       return "pause";
734    case TR_TRACKER_ERROR:      return "error";
735    default:
736      assert(!"unknown status code");
737      return NULL;
738  }
739}
740
741void
742makeinfowind(struct cbdata *data, int index) {
743  tr_stat_t *sb;
744  GtkWidget *wind, *table, *name, *value;
745  char buf[32];
746
747  if(index >= tr_torrentStat(data->tr, &sb)) {
748    assert(!"XXX i'm tired");
749  }
750  wind = gtk_dialog_new_with_buttons(sb[index].info.name, GTK_WINDOW(data->wind),
751    GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
752
753  table = gtk_table_new(21, 2, FALSE);
754
755  name = gtk_label_new("Torrent File");
756  value = gtk_label_new(sb[index].info.torrent);
757  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
758  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
759  name = gtk_label_new("Name");
760  value = gtk_label_new(sb[index].info.name);
761  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
762  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
763  name = gtk_label_new("Tracker Address");
764  value = gtk_label_new(sb[index].info.trackerAddress);
765  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
766  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
767  name = gtk_label_new("Tracker Port");
768  snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
769  value = gtk_label_new(buf);
770  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
771  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
772  name = gtk_label_new("Tracker Announce URL");
773  value = gtk_label_new(sb[index].info.trackerAnnounce);
774  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
775  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
776  name = gtk_label_new("Piece Size");
777  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
778  value = gtk_label_new(buf);
779  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
780  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
781  name = gtk_label_new("Piece Count");
782  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
783  value = gtk_label_new(buf);
784  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
785  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
786  name = gtk_label_new("Total Size");
787  snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
788  value = gtk_label_new(buf);
789  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
790  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
791  name = gtk_label_new("File Count");
792  snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
793  value = gtk_label_new(buf);
794  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
795  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
796  name = gtk_label_new("Status");
797  value = gtk_label_new(statusstr(sb[index].status));
798  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
799  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
800  name = gtk_label_new("Error");
801  value = gtk_label_new(sb[index].error);
802  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
803  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
804  name = gtk_label_new("Progress");
805  snprintf(buf, sizeof buf, "%f", sb[index].progress);
806  value = gtk_label_new(buf);
807  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
808  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
809  name = gtk_label_new("Download Rate");
810  value = gtk_label_new(buf);
811  snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
812  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
813  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
814  name = gtk_label_new("Upload Rate");
815  snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
816  value = gtk_label_new(buf);
817  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
818  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
819  name = gtk_label_new("ETA");
820  snprintf(buf, sizeof buf, "%i", sb[index].eta);
821  value = gtk_label_new(buf);
822  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
823  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
824  name = gtk_label_new("Total Peers");
825  snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
826  value = gtk_label_new(buf);
827  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
828  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
829  name = gtk_label_new("Uploading Peers");
830  snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
831  value = gtk_label_new(buf);
832  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
833  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
834  name = gtk_label_new("Downloading Peers");
835  snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
836  value = gtk_label_new(buf);
837  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
838  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
839  name = gtk_label_new("Downloaded");
840  snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
841  value = gtk_label_new(buf);
842  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
843  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
844  name = gtk_label_new("Uploaded");
845  snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
846  value = gtk_label_new(buf);
847  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
848  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
849
850  gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
851  g_signal_connect(G_OBJECT(wind), "response",
852                   G_CALLBACK(gtk_widget_destroy), NULL);
853  gtk_widget_show_all(wind);
854  free(sb);
855}
856
857gboolean
858savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat) {
859  char *errstr;
860  tr_stat_t *st;
861  gboolean ret;
862
863  assert(NULL != tr || 0 <= count);
864
865  if(0 <= count)
866    ret = cf_savestate(count, stat, &errstr);
867  else {
868    count = tr_torrentStat(tr, &st);
869    ret = cf_savestate(count, st, &errstr);
870    free(st);
871  }
872
873  if(!ret) {
874    errmsg(wind, "%s", errstr);
875    g_free(errstr);
876  }
877
878  return ret;
879}
Note: See TracBrowser for help on using the repository browser.