source: trunk/gtk/main.c @ 10

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

Update 2005-12-01

File size: 27.6 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  /* remove any excess rows */
476  if(gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter))
477    while(gtk_list_store_remove(data->model, &iter))
478      ;
479
480  return TRUE;
481}
482
483gboolean
484listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
485  struct cbdata *data = gdata;
486
487  if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
488    dopopupmenu(widget, event, data);
489    return TRUE;
490  }
491
492  return FALSE;
493}
494
495gboolean
496listpopup(GtkWidget *widget, gpointer userdata) {
497  dopopupmenu(widget, NULL, userdata);
498
499  return TRUE;
500}
501
502void
503dopopupmenu(GtkWidget *widget SHUTUP, GdkEventButton *event,
504            struct cbdata *data) {
505  GtkWidget *menu = gtk_menu_new();
506  GtkWidget *item;
507  GtkTreePath *path;
508  GtkTreeIter iter;
509  unsigned int ii;
510  int index;
511
512  index = -1;
513  if(NULL != event && gtk_tree_view_get_path_at_pos(
514       data->view, event->x, event->y, &path, NULL, NULL, NULL)) {
515    if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
516      gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter, MC_ROW_INDEX, &index, -1);
517    gtk_tree_path_free(path);
518  }
519
520  /* XXX am I leaking references here? */
521  /* XXX can I cache this in cbdata? */
522  for(ii = 0; ii < ALEN(actionitems); ii++) {
523    item = gtk_menu_item_new_with_label(actionitems[ii].name);
524    g_object_set_data(G_OBJECT(item), LIST_ACTION,
525                      GINT_TO_POINTER(actionitems[ii].act));
526    g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
527                      GINT_TO_POINTER(FROM_POPUP));
528    g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
529    g_signal_connect(G_OBJECT(item), "activate",
530                     G_CALLBACK(actionclick), data);
531    gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
532  }
533
534  gtk_widget_show_all(menu);
535
536  gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
537                 (NULL == event ? 0 : event->button),
538                 gdk_event_get_time((GdkEvent*)event));
539}
540
541void
542actionclick(GtkWidget *widget, gpointer gdata) {
543  struct cbdata *data = gdata;
544  GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
545  GtkTreeModel *model;
546  GtkTreeIter iter;
547  enum listact act =
548    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
549  enum listfrom from =
550    GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
551  int index;
552  tr_stat_t *sb;
553
554  switch(act) {
555    case ACT_OPEN:
556      makeaddwind(data);
557      return;
558    case ACT_PREF:
559      makeprefwindow(data->wind, data->tr);
560      return;
561    default:
562      break;
563  }
564
565  index = -1;
566  switch(from) {
567    case FROM_BUTTON:
568      if(gtk_tree_selection_get_selected(sel, &model, &iter))
569        gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
570                           MC_ROW_INDEX, &index, -1);
571      /* XXX should I assert(0 <= index) to insure a row was selected? */
572      break;
573    case FROM_POPUP:
574      index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
575      break;
576    default:
577      assert(!"unknown action source");
578      break;
579  }
580
581  if(0 <= index) {
582    switch(act) {
583      case ACT_START:
584        tr_torrentStart(data->tr, index);
585        savetorrents(data->tr, data->wind, -1, NULL);
586        break;
587      case ACT_STOP:
588        tr_torrentStop(data->tr, index);
589        savetorrents(data->tr, data->wind, -1, NULL);
590        break;
591      case ACT_DELETE:
592        /* XXX need to be able to stat just one torrent */
593        if(index >= tr_torrentStat(data->tr, &sb)) {
594          assert(!"XXX i'm tired");
595        }
596        if(TR_TORRENT_NEEDS_STOP(sb[index].status))
597          tr_torrentStop(data->tr, index);
598        free(sb);
599        tr_torrentClose(data->tr, index);
600        savetorrents(data->tr, data->wind, -1, NULL);
601        updatemodel(data);
602        break;
603      case ACT_INFO:
604        makeinfowind(data, index);
605        break;
606      default:
607        assert(!"unknown type");
608        break;
609    }
610  }
611}
612
613void
614makeaddwind(struct cbdata *data) {
615  GtkWidget *wind = gtk_file_selection_new("Add a Torrent");
616
617  g_object_set_data(G_OBJECT(GTK_FILE_SELECTION(wind)->ok_button),
618                    CBDATA_PTR, data);
619  g_signal_connect(GTK_FILE_SELECTION(wind)->ok_button, "clicked",
620                   G_CALLBACK(fileclick), wind);
621  g_signal_connect_swapped(GTK_FILE_SELECTION(wind)->cancel_button, "clicked",
622                           G_CALLBACK(gtk_widget_destroy), wind); 
623  gtk_window_set_transient_for(GTK_WINDOW(wind), data->wind);
624  gtk_window_set_destroy_with_parent(GTK_WINDOW(wind), TRUE);
625  gtk_window_set_modal(GTK_WINDOW(wind), TRUE);
626  gtk_widget_show_all(wind);
627}
628
629gboolean
630addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
631           const char *dir, gboolean paused) {
632  char *wd;
633
634  if(NULL == dir) {
635    dir = cf_getpref(PREF_DIR);
636    if(!mkdir_p(dir, 0777)) {
637      errmsg(parentwind, "Failed to create download directory %s:\n%s",
638             dir, strerror(errno));
639      return FALSE;
640    }
641  }
642
643  if(0 != tr_torrentInit(tr, torrent)) {
644    /* XXX would be nice to have errno strings, are they printed to stdout? */
645    errmsg(parentwind, "Failed to open torrent file %s", torrent);
646    return FALSE;
647  }
648
649  if(NULL != dir)
650    tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
651  else {
652    wd = g_new(char, MAXPATHLEN + 1);
653    if(NULL == getcwd(wd, MAXPATHLEN + 1))
654      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
655    else {
656      tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
657      free(wd);
658    }
659  }
660
661  if(!paused)
662    tr_torrentStart(tr, tr_torrentCount(tr) - 1);
663  return TRUE;
664}
665
666void
667fileclick(GtkWidget *widget, gpointer gdata) {
668  struct cbdata *data = g_object_get_data(G_OBJECT(widget), CBDATA_PTR);
669  GtkWidget *wind = gdata;
670  const char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(wind));
671
672  if(addtorrent(data->tr, data->wind, file, NULL, FALSE)) {
673    updatemodel(data);
674    savetorrents(data->tr, data->wind, -1, NULL);
675  }
676
677  gtk_widget_destroy(wind);
678}
679
680const char *
681statusstr(int status) {
682  switch(status) {
683    case TR_STATUS_CHECK:       return "check";
684    case TR_STATUS_DOWNLOAD:    return "download";
685    case TR_STATUS_SEED:        return "seed";
686    case TR_STATUS_STOPPING:    return "stopping";
687    case TR_STATUS_STOPPED:     return "stopped";
688    case TR_STATUS_PAUSE:       return "pause";
689    case TR_TRACKER_ERROR:      return "error";
690    default:
691      assert(!"unknown status code");
692      return NULL;
693  }
694}
695
696void
697makeinfowind(struct cbdata *data, int index) {
698  tr_stat_t *sb;
699  GtkWidget *wind, *table, *name, *value;
700  char buf[32];
701
702  if(index >= tr_torrentStat(data->tr, &sb)) {
703    assert(!"XXX i'm tired");
704  }
705  wind = gtk_dialog_new_with_buttons(sb[index].info.name, data->wind,
706    GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
707
708  table = gtk_table_new(21, 2, FALSE);
709
710  name = gtk_label_new("Torrent File");
711  value = gtk_label_new(sb[index].info.torrent);
712  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
713  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
714  name = gtk_label_new("Name");
715  value = gtk_label_new(sb[index].info.name);
716  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
717  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
718  name = gtk_label_new("Tracker Address");
719  value = gtk_label_new(sb[index].info.trackerAddress);
720  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
721  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
722  name = gtk_label_new("Tracker Port");
723  snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
724  value = gtk_label_new(buf);
725  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
726  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
727  name = gtk_label_new("Tracker Announce URL");
728  value = gtk_label_new(sb[index].info.trackerAnnounce);
729  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
730  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
731  name = gtk_label_new("Piece Size");
732  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
733  value = gtk_label_new(buf);
734  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
735  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
736  name = gtk_label_new("Piece Count");
737  snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
738  value = gtk_label_new(buf);
739  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
740  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
741  name = gtk_label_new("Total Size");
742  snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
743  value = gtk_label_new(buf);
744  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
745  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
746  name = gtk_label_new("File Count");
747  snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
748  value = gtk_label_new(buf);
749  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
750  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
751  name = gtk_label_new("Status");
752  value = gtk_label_new(statusstr(sb[index].status));
753  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
754  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
755  name = gtk_label_new("Error");
756  value = gtk_label_new(sb[index].error);
757  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
758  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
759  name = gtk_label_new("Progress");
760  snprintf(buf, sizeof buf, "%f", sb[index].progress);
761  value = gtk_label_new(buf);
762  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
763  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
764  name = gtk_label_new("Download Rate");
765  value = gtk_label_new(buf);
766  snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
767  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
768  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
769  name = gtk_label_new("Upload Rate");
770  snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
771  value = gtk_label_new(buf);
772  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
773  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
774  name = gtk_label_new("ETA");
775  snprintf(buf, sizeof buf, "%i", sb[index].eta);
776  value = gtk_label_new(buf);
777  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
778  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
779  name = gtk_label_new("Total Peers");
780  snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
781  value = gtk_label_new(buf);
782  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
783  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
784  name = gtk_label_new("Uploading Peers");
785  snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
786  value = gtk_label_new(buf);
787  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
788  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
789  name = gtk_label_new("Downloading Peers");
790  snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
791  value = gtk_label_new(buf);
792  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
793  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
794  name = gtk_label_new("Downloaded");
795  snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
796  value = gtk_label_new(buf);
797  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
798  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
799  name = gtk_label_new("Uploaded");
800  snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
801  value = gtk_label_new(buf);
802  gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
803  gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
804
805  gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
806  g_signal_connect(G_OBJECT(wind), "response",
807                   G_CALLBACK(gtk_widget_destroy), NULL);
808  gtk_widget_show_all(wind);
809  free(sb);
810}
811
812gboolean
813savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat) {
814  char *errstr;
815  tr_stat_t *st;
816  gboolean ret;
817
818  assert(NULL != tr || 0 <= count);
819
820  if(0 <= count)
821    ret = cf_savestate(count, stat, &errstr);
822  else {
823    count = tr_torrentStat(tr, &st);
824    ret = cf_savestate(count, st, &errstr);
825    free(st);
826  }
827
828  if(!ret) {
829    errmsg(wind, "%s", errstr);
830    g_free(errstr);
831  }
832
833  return ret;
834}
Note: See TracBrowser for help on using the repository browser.