source: trunk/gtk/main.c @ 7

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

Update 2005-11-24

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