Update 2005-11-18

This commit is contained in:
Eric Petit
2006-01-12 18:32:29 +00:00
parent e877994b21
commit aad7bf22cb
11 changed files with 1712 additions and 17 deletions

15
gtk/Jamfile Normal file
View File

@@ -0,0 +1,15 @@
{
SubDir TOP gtk ;
GTK_SRC = conf.c main.c util.c ;
local CCFLAGS = $(CCFLAGS) $(GTKCCFLAGS) ;
#local LINKLIBS = $(LINKLIBS) $(GTKLINKLIBS) ;
local HDRS = $(HDRS) $(TOP)/libtransmission ;
# jesus fucking christ, I give up
LINKLIBS += $(GTKLINKLIBS) ;
Main transmission-gtk : $(GTK_SRC) ;
LinkLibraries transmission-gtk : libtransmission.a ;
}

489
gtk/conf.c Normal file
View File

@@ -0,0 +1,489 @@
/*
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include "conf.h"
#include "transmission.h"
#define FILE_LOCK "gtk_lock"
#define FILE_PREFS "gtk_prefs"
#define FILE_PREFS_TMP "gtk_prefs.tmp"
#define FILE_STATE "gtk_state"
#define FILE_STATE_TMP "gtk_state.tmp"
#define PREF_SEP_KEYVAL '\t'
#define PREF_SEP_LINE '\n'
#define STATE_SEP '\n'
static int
lockfile(const char *file, char **errstr);
static gboolean
writeprefs(char **errstr);
static gboolean
writefile_traverse(gpointer key, gpointer value, gpointer data);
static char *
getstateval(struct cf_torrentstate *state, char *line);
static char *confdir = NULL;
static GTree *prefs = NULL;
static int
lockfile(const char *file, char **errstr) {
int fd, savederr;
struct flock lk;
*errstr = NULL;
if(0 > (fd = open(file, O_RDWR | O_CREAT, 0666))) {
savederr = errno;
*errstr = g_strdup_printf("Error opening file %s for writing:\n%s",
file, strerror(errno));
errno = savederr;
return -1;
}
bzero(&lk, sizeof(lk));
lk.l_start = 0;
lk.l_len = 0;
lk.l_type = F_WRLCK;
lk.l_whence = SEEK_SET;
if(-1 == fcntl(fd, F_SETLK, &lk)) {
savederr = errno;
if(EAGAIN == errno)
*errstr = g_strdup_printf("Another copy of %s is already running.",
g_get_application_name());
else
*errstr = g_strdup_printf("Error obtaining lock on file %s:\n%s",
file, strerror(errno));
close(fd);
errno = savederr;
return -1;
}
return fd;
}
gboolean
cf_init(const char *dir, char **errstr) {
struct stat sb;
*errstr = NULL;
confdir = g_strdup(dir);
if(0 > stat(dir, &sb)) {
if(ENOENT != errno)
*errstr = g_strdup_printf("Failed to check directory %s:\n%s",
dir, strerror(errno));
else {
if(0 == mkdir(dir, 0777))
return TRUE;
else
*errstr = g_strdup_printf("Failed to create directory %s:\n%s",
dir, strerror(errno));
}
return FALSE;
}
if(S_IFDIR & sb.st_mode)
return TRUE;
*errstr = g_strdup_printf("%s is not a directory", dir);
return FALSE;
}
gboolean
cf_lock(char **errstr) {
char *path = g_build_filename(confdir, FILE_LOCK, NULL);
int fd = lockfile(path, errstr);
g_free(path);
return 0 <= fd;
}
gboolean
cf_loadprefs(char **errstr) {
char *path = g_build_filename(confdir, FILE_PREFS, NULL);
GIOChannel *io;
GError *err;
char *line, *sep;
gsize len, termpos;
char term = PREF_SEP_LINE;
*errstr = NULL;
if(NULL != prefs)
g_tree_destroy(prefs);
prefs = g_tree_new_full((GCompareDataFunc)g_ascii_strcasecmp, NULL,
g_free, g_free);
err = NULL;
io = g_io_channel_new_file(path, "r", &err);
if(NULL != err) {
if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
*errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
path, err->message);
goto done;
}
/*g_io_channel_set_encoding(io, NULL, NULL);*/
g_io_channel_set_line_term(io, &term, 1);
err = NULL;
for(;;) {
assert(NULL == err) ;
switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
case G_IO_STATUS_ERROR:
*errstr = g_strdup_printf("Error reading file %s:\n%s",
path, err->message);
goto done;
case G_IO_STATUS_NORMAL:
if(NULL != line) {
if(NULL != (sep = strchr(line, PREF_SEP_KEYVAL)) && sep > line) {
*sep = '\0';
line[termpos] = '\0';
g_tree_insert(prefs, g_strcompress(line), g_strcompress(sep + 1));
}
g_free(line);
}
break;
case G_IO_STATUS_EOF:
goto done;
default:
assert(!"unknown return code");
goto done;
}
}
done:
if(NULL != err)
g_error_free(err);
if(NULL != io)
g_io_channel_unref(io);
return NULL == *errstr;
}
const char *
cf_getpref(const char *name) {
assert(NULL != prefs);
return g_tree_lookup(prefs, name);
}
gboolean
cf_setpref(const char *name, const char *value, char **errstr) {
assert(NULL != prefs);
g_tree_insert(prefs, g_strdup(name), g_strdup(value));
return writeprefs(errstr);
}
struct writeinfo {
GIOChannel *io;
GError *err;
};
static gboolean
writeprefs(char **errstr) {
char *file = g_build_filename(confdir, FILE_PREFS, NULL);
char *tmpfile = g_build_filename(confdir, FILE_PREFS_TMP, NULL);
GIOChannel *io = NULL;
struct writeinfo info;
int fd;
assert(NULL != prefs);
*errstr = NULL;
if(0 > (fd = lockfile(tmpfile, errstr))) {
g_free(errstr);
*errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
tmpfile, strerror(errno));
goto done;
}
info.err = NULL;
io = g_io_channel_unix_new(fd);
/*g_io_channel_set_encoding(io, NULL, NULL);*/
g_io_channel_set_close_on_unref(io, TRUE);
info.io = io;
info.err = NULL;
g_tree_foreach(prefs, writefile_traverse, &info);
if(NULL != info.err ||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &info.err)) {
*errstr = g_strdup_printf("Error writing to file %s:\n%s",
tmpfile, info.err->message);
g_error_free(info.err);
goto done;
}
if(0 > rename(tmpfile, file)) {
*errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
tmpfile, file, strerror(errno));
goto done;
}
done:
g_free(file);
g_free(tmpfile);
if(NULL != io)
g_io_channel_unref(io);
return NULL == *errstr;
}
static gboolean
writefile_traverse(gpointer key, gpointer value, gpointer data) {
struct writeinfo *info = data;
char *ekey, *eval, *line;
char sep[2];
int len;
ekey = g_strescape(key, NULL);
eval = g_strescape(value, NULL);
sep[0] = PREF_SEP_KEYVAL;
sep[1] = '\0';
line = g_strjoin(sep, ekey, eval, NULL);
len = strlen(line);
line[len] = PREF_SEP_LINE;
switch(g_io_channel_write_chars(info->io, line, len + 1, NULL, &info->err)) {
case G_IO_STATUS_ERROR:
goto done;
case G_IO_STATUS_NORMAL:
break;
default:
assert(!"unknown return code");
goto done;
}
done:
g_free(ekey);
g_free(eval);
g_free(line);
return NULL != info->err;
}
GList *
cf_loadstate(char **errstr) {
char *path = g_build_filename(confdir, FILE_STATE, NULL);
GIOChannel *io;
GError *err;
char term = STATE_SEP;
GList *ret = NULL;
char *line, *ptr;
gsize len, termpos;
struct cf_torrentstate *ts;
err = NULL;
io = g_io_channel_new_file(path, "r", &err);
if(NULL != err) {
if(!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT))
*errstr = g_strdup_printf("Error opening file %s for reading:\n%s",
path, err->message);
goto done;
}
/*g_io_channel_set_encoding(io, NULL, NULL);*/
g_io_channel_set_line_term(io, &term, 1);
err = NULL;
for(;;) {
assert(NULL == err);
switch(g_io_channel_read_line(io, &line, &len, &termpos, &err)) {
case G_IO_STATUS_ERROR:
*errstr = g_strdup_printf("Error reading file %s:\n%s",
path, err->message);
goto done;
case G_IO_STATUS_NORMAL:
if(NULL != line) {
ts = g_new0(struct cf_torrentstate, 1);
ptr = line;
while(NULL != (ptr = getstateval(ts, ptr)))
;
g_free(line);
if(NULL != ts->ts_torrent && NULL != ts->ts_directory)
ret = g_list_append(ret, ts);
else
cf_freestate(ts);
}
break;
case G_IO_STATUS_EOF:
goto done;
default:
assert(!"unknown return code");
goto done;
}
}
done:
if(NULL != err)
g_error_free(err);
if(NULL != io)
g_io_channel_unref(io);
if(NULL != *errstr && NULL != ret) {
g_list_foreach(ret, (GFunc)g_free, NULL);
g_list_free(ret);
ret = NULL;
}
return ret;
}
static char *
getstateval(struct cf_torrentstate *state, char *line) {
char *start, *end;
while(isspace(*line))
line++;
if(NULL == (start = strchr(line, '=')))
return NULL;
while(isspace(*(++start)))
;
if('"' != *start)
return NULL;
for(end = ++start; '\0' != *end && '"' != *end; end++)
if('\\' == *end && '\0' != *(end + 1))
end++;
if('"' != *end)
return NULL;
if(0 == memcmp(line, "torrent", sizeof("torrent") - 1)) {
state->ts_torrent = g_new(char, end - start + 1);
memcpy(state->ts_torrent, start, end - start);
state->ts_torrent[end - start] = '\0';
}
else if(0 == memcmp(line, "dir", sizeof("dir") - 1)) {
state->ts_directory = g_new(char, end - start + 1);
memcpy(state->ts_directory, start, end - start);
state->ts_directory[end - start] = '\0';
}
else if(0 == memcmp(line, "paused", sizeof("paused") - 1)) {
state->ts_paused = (0 == memcmp("yes", start, end - start));
}
return end + 1;
}
/* XXX need to save download directory, also maybe running/stopped state */
gboolean
cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
char *file = g_build_filename(confdir, FILE_STATE, NULL);
char *tmpfile = g_build_filename(confdir, FILE_STATE_TMP, NULL);
GIOChannel *io = NULL;
GError *err;
int fd, ii;
char *torrentfile, *torrentdir, *line;
gsize written;
gboolean paused;
GIOStatus res;
*errstr = NULL;
if(0 > (fd = lockfile(tmpfile, errstr))) {
g_free(errstr);
*errstr = g_strdup_printf("Error opening or locking file %s:\n%s",
tmpfile, strerror(errno));
goto done;
}
io = g_io_channel_unix_new(fd);
/* XXX what the hell should I be doing about unicode? */
/*g_io_channel_set_encoding(io, NULL, NULL);*/
g_io_channel_set_close_on_unref(io, TRUE);
err = NULL;
for(ii = 0; ii < count; ii++) {
/* XXX need a better way to query running/stopped state */
paused = (TR_STATUS_STOPPING == torrents[ii].status ||
TR_STATUS_STOPPED == torrents[ii].status ||
TR_STATUS_PAUSE == torrents[ii].status);
torrentfile = g_strescape(torrents[ii].info.torrent, "");
torrentdir = g_strescape(torrents[ii].folder, "");
/* g_strcompress */
line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
torrentfile, torrentdir, (paused ? "yes" : "no"),
STATE_SEP);
res = g_io_channel_write_chars(io, line, strlen(line), &written, &err);
g_free(torrentfile);
g_free(torrentdir);
g_free(line);
switch(res) {
case G_IO_STATUS_ERROR:
goto done;
case G_IO_STATUS_NORMAL:
break;
default:
assert(!"unknown return code");
goto done;
}
}
if(NULL != err ||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {
*errstr = g_strdup_printf("Error writing to file %s:\n%s",
tmpfile, err->message);
g_error_free(err);
goto done;
}
if(0 > rename(tmpfile, file)) {
*errstr = g_strdup_printf("Error renaming %s to %s:\n%s",
tmpfile, file, strerror(errno));
goto done;
}
done:
g_free(file);
g_free(tmpfile);
if(NULL != io)
g_io_channel_unref(io);
return NULL == *errstr;
}
void
cf_freestate(struct cf_torrentstate *state) {
if(NULL != state->ts_torrent)
g_free(state->ts_torrent);
if(NULL != state->ts_directory)
g_free(state->ts_directory);
g_free(state);
}

55
gtk/conf.h Normal file
View File

@@ -0,0 +1,55 @@
/*
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TG_CONF_H
#define TG_CONF_H
#include "transmission.h"
struct cf_torrentstate {
char *ts_torrent;
char *ts_directory;
gboolean ts_paused;
};
gboolean
cf_init(const char *confdir, char **errstr);
gboolean
cf_lock(char **errstr);
gboolean
cf_loadprefs(char **errstr);
const char *
cf_getpref(const char *name);
gboolean
cf_setpref(const char *name, const char *value, char **errstr);
GList *
cf_loadstate(char **errstr);
gboolean
cf_savestate(int count, tr_stat_t *torrents, char **errstr);
void
cf_freestate(struct cf_torrentstate *state);
#endif /* TG_CONF_H */

879
gtk/main.c Normal file
View File

@@ -0,0 +1,879 @@
/*
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <time.h>
#include <unistd.h>
#include <gtk/gtk.h>
#include "conf.h"
#include "transmission.h"
#include "util.h"
#define TRACKER_EXIT_TIMEOUT 30
struct cbdata {
tr_handle_t *tr;
GtkWidget *wind;
GtkListStore *model;
GtkTreeView *view;
guint timer;
};
struct exitdata {
struct cbdata *cbdata;
time_t started;
guint timer;
};
struct pieces {
char p[120];
};
void
readargs(int argc, char **argv, int *port, int *limit);
void
maketypes(void);
gpointer
tr_pieces_copy(gpointer);
void
tr_pieces_free(gpointer);
void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
gboolean
winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
gboolean
exitcheck(gpointer gdata);
GtkWidget *
makewind_toolbar(struct cbdata *data);
GtkWidget *
makewind_list(struct cbdata *data);
gboolean
updatemodel(gpointer gdata);
gboolean
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
gboolean
listpopup(GtkWidget *widget, gpointer userdata);
void
dopopupmenu(GtkWidget *widget, GdkEventButton *event, struct cbdata *data);
void
actionclick(GtkWidget *widget, gpointer gdata);
void
makeaddwind(struct cbdata *data);
gboolean
addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
const char *dir, gboolean paused);
void
fileclick(GtkWidget *widget, gpointer gdata);
const char *
statusstr(int status);
void
makeinfowind(struct cbdata *data, int index);
gboolean
savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat);
#define TR_TYPE_PIECES_NAME "tr-type-pieces"
#define TR_TYPE_PIECES ((const GType)tr_type_pieces)
#define TR_PIECES(ptr) ((struct pieces*)ptr)
GType tr_type_pieces;
#define LIST_ACTION "torrent-list-action"
enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO };
#define LIST_ACTION_FROM "torrent-list-action-from"
enum listfrom { FROM_BUTTON, FROM_POPUP };
#define LIST_INDEX "torrent-list-index"
struct { gint pos; const gchar *name; const gchar *id; enum listact act;
const char *ttext; const char *tpriv; }
actionitems[] = {
{0, "Add", GTK_STOCK_ADD, ACT_OPEN,
"Add a new torrent file", "XXX"},
{1, "Resume", GTK_STOCK_MEDIA_PLAY, ACT_START,
"Resume a torrent that has been paused", "XXX"},
{2, "Pause", GTK_STOCK_MEDIA_PAUSE, ACT_STOP,
"Pause a torrent", "XXX"},
{3, "Remove", GTK_STOCK_REMOVE, ACT_DELETE,
"Remove a torrent from the list", "XXX"},
{4, "Properties", GTK_STOCK_PROPERTIES, ACT_INFO,
"Get additional information for a torrent", "XXX"},
};
#define CBDATA_PTR "callback-data-pointer"
int
main(int argc, char **argv) {
GtkWidget *mainwind, *preferr, *stateerr;
char *err;
int port, limit;
tr_handle_t *tr;
GList *saved;
gtk_init(&argc, &argv);
readargs(argc, argv, &port, &limit);
tr = tr_init();
if(cf_init(tr_getPrefsDirectory(tr), &err)) {
if(cf_lock(&err)) {
/* create main window now so any error dialogs can be it's children */
mainwind = gtk_window_new(GTK_WINDOW_TOPLEVEL);
preferr = NULL;
stateerr = NULL;
if(!cf_loadprefs(&err)) {
preferr = errmsg(mainwind, "%s", err);
g_free(err);
}
saved = cf_loadstate(&err);
if(NULL != err) {
stateerr = errmsg(mainwind, "%s", err);
g_free(err);
}
/* XXX need to remove port and limit options and make them prefs */
/* XXX need prefs gui */
/* XXX need default save dir pref */
if(0 != port)
tr_setBindPort(tr, port);
if(0 != limit)
tr_setUploadLimit(tr, limit);
maketypes();
makewind(mainwind, tr, saved);
if(NULL != preferr)
gtk_widget_show_all(preferr);
if(NULL != stateerr)
gtk_widget_show_all(stateerr);
} else {
errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
g_free(err);
}
} else {
errmsg_full(NULL, (errfunc_t)gtk_main_quit, NULL, "%s", err);
g_free(err);
}
gtk_main();
return 0;
}
void
readargs(int argc, char **argv, int *port, int *limit) {
char *name;
int opt, num;
*port = 0;
*limit = 0;
if(NULL == (name = strrchr(argv[0], '/')) || '\0' == *(++name))
name = argv[0];
for(num = 1; num < argc; num++)
if(0 == strcmp(argv[num], "-help") || 0 == strcmp(argv[num], "--help"))
goto usage;
while(0 <= (opt = getopt(argc, argv, "hp:u:"))) {
switch(opt) {
case 'p':
num = atoi(optarg);
if(0 < num && 0xffff > num)
*port = num;
break;
case 'u':
num = atoi(optarg);
if(0 != num)
*limit = num;
break;
default:
goto usage;
}
}
return;
usage:
printf("usage: %s [-h] [-p port] [-u limit]\n", name);
exit(1);
}
void
maketypes(void) {
tr_type_pieces = g_boxed_type_register_static(
TR_TYPE_PIECES_NAME, tr_pieces_copy, tr_pieces_free);
}
gpointer
tr_pieces_copy(gpointer data) {
return g_memdup(data, sizeof(struct pieces));
}
void
tr_pieces_free(gpointer data) {
g_free(data);
}
void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
struct cbdata *data = g_new0(struct cbdata, 1);
GtkWidget *list;
GtkRequisition req;
GList *ii;
struct cf_torrentstate *ts;
data->tr = tr;
data->wind = wind;
data->timer = -1;
/* filled in by makewind_list */
data->model = NULL;
data->view = NULL;
gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
list = makewind_list(data);
gtk_widget_size_request(list, &req);
gtk_container_add(GTK_CONTAINER(scroll), list);
gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 5);
gtk_container_add(GTK_CONTAINER(wind), vbox);
gtk_window_set_default_size(GTK_WINDOW(wind), req.width, req.height);
g_signal_connect(G_OBJECT(wind), "delete_event", G_CALLBACK(winclose), data);
gtk_widget_show_all(wind);
for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
ts = ii->data;
addtorrent(tr, wind, ts->ts_torrent, ts->ts_directory, ts->ts_paused);
cf_freestate(ts);
}
g_list_free(saved);
data->timer = g_timeout_add(500, updatemodel, data);
updatemodel(data);
}
/* XXX is this the right thing to do? */
#define TR_TORRENT_NEEDS_STOP(t) \
((t) & TR_STATUS_CHECK || (t) & TR_STATUS_DOWNLOAD || (t) & TR_STATUS_SEED)
gboolean
winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
struct cbdata *data = gdata;
struct exitdata *edata;
tr_stat_t *st;
int ii;
if(0 >= data->timer)
g_source_remove(data->timer);
data->timer = -1;
for(ii = tr_torrentStat(data->tr, &st); 0 < ii; ii--) {
if(TR_TORRENT_NEEDS_STOP(st[ii-1].status)) {
fprintf(stderr, "quit: stopping %i %s\n", ii, st[ii-1].info.name);
tr_torrentStop(data->tr, ii - 1);
} else {
fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
tr_torrentClose(data->tr, ii - 1);
}
}
free(st);
/* XXX should disable widgets or something */
/* try to wait until torrents stop before exiting */
edata = g_new0(struct exitdata, 1);
edata->cbdata = data;
edata->started = time(NULL);
edata->timer = g_timeout_add(500, exitcheck, edata);
fprintf(stderr, "quit: starting timeout at %i\n", edata->started);
//exitcheck(edata);
/* returning FALSE means to destroy the window */
return TRUE;
}
gboolean
exitcheck(gpointer gdata) {
struct exitdata *data = gdata;
tr_stat_t *st;
int ii;
for(ii = tr_torrentStat(data->cbdata->tr, &st); 0 < ii; ii--) {
if(TR_STATUS_PAUSE == st[ii-1].status) {
fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);
tr_torrentClose(data->cbdata->tr, ii - 1);
}
}
free(st);
fprintf(stderr, "quit: %i torrents left at %i\n",
tr_torrentCount(data->cbdata->tr), time(NULL));
/* keep going if we still have torrents and haven't hit the exit timeout */
if(0 < tr_torrentCount(data->cbdata->tr) &&
time(NULL) - data->started < TRACKER_EXIT_TIMEOUT) {
updatemodel(data->cbdata);
return TRUE;
}
fprintf(stderr, "quit: giving up on %i torrents\n",
tr_torrentCount(data->cbdata->tr));
for(ii = tr_torrentCount(data->cbdata->tr); 0 < ii; ii--)
tr_torrentClose(data->cbdata->tr, ii - 1);
/* exit otherwise */
if(0 >= data->timer)
g_source_remove(data->timer);
data->timer = -1;
gtk_widget_destroy(data->cbdata->wind);
tr_close(data->cbdata->tr);
g_free(data->cbdata);
g_free(data);
gtk_main_quit();
return FALSE;
}
GtkWidget *
makewind_toolbar(struct cbdata *data) {
GtkWidget *bar = gtk_toolbar_new();
GtkToolItem *item;
unsigned int ii;
gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
item = gtk_tool_button_new_from_stock(actionitems[ii].id);
gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), actionitems[ii].name);
gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
actionitems[ii].ttext, actionitems[ii].tpriv);
g_object_set_data(G_OBJECT(item), LIST_ACTION,
GINT_TO_POINTER(actionitems[ii].act));
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
GINT_TO_POINTER(FROM_BUTTON));
g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
gtk_toolbar_insert(GTK_TOOLBAR(bar), GTK_TOOL_ITEM(item), actionitems[ii].pos);
}
return bar;
}
enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
MC_ROW_INDEX, MC_ROW_COUNT};
GtkWidget *
makewind_list(struct cbdata *data) {
GType types[] = {
/* info->name, info->totalSize, status, error, progress */
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT,
/* rateDownload, rateUpload, eta, peersTotal, peersUploading */
G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
/* peersDownloading, pieces, downloaded, uploaded */
G_TYPE_INT, TR_TYPE_PIECES, G_TYPE_UINT64, G_TYPE_UINT64,
/* index into the torrent array */
G_TYPE_INT};
GtkListStore *model;
GtkWidget *view;
/*GtkTreeViewColumn *col;*/
GtkTreeSelection *sel;
GtkCellRenderer *rend;
GtkCellRenderer *rendprog;
assert(MC_ROW_COUNT == sizeof(types) / sizeof(types[0]));
model = gtk_list_store_newv(MC_ROW_COUNT, types);
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
/* XXX do I need to worry about reference counts anywhere else? */
g_object_unref(G_OBJECT(model));
data->model = model;
data->view = GTK_TREE_VIEW(view);
rend = gtk_cell_renderer_text_new();
rendprog = gtk_cell_renderer_progress_new();
g_object_set(rendprog, "text", "", NULL);
/*
col = gtk_tree_view_column_new_with_attributes(
"Name", rend, "text", MC_NAME, NULL);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_SIZE);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
col = gtk_tree_view_column_new_with_attributes(
"Status", rend, "text", MC_STAT, NULL);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_ERR);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DPEERS);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_UPEERS);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
col = gtk_tree_view_column_new_with_attributes(
"Progress", rendprog, "value", MC_PROG, NULL);
gtk_tree_view_column_pack_start(col, rend, TRUE);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_ETA);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DRATE);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_URATE);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_DOWN);
gtk_tree_view_column_add_attribute(col, rend, "text", MC_UP);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
*/
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Name", rend,
"text", MC_NAME, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Size", rend,
"text", MC_SIZE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Status", rend,
"text", MC_STAT, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Error", rend,
"text", MC_ERR, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Progress", rendprog,
"value", MC_PROG, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Download Rate", rend,
"text", MC_DRATE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Upload Rate", rend,
"text", MC_URATE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("ETA", rend,
"text", MC_ETA, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Peers", rend,
"text", MC_PEERS, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Seeders", rend,
"text", MC_UPEERS, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Leechers", rend,
"text", MC_DPEERS, NULL));
/*gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("", rend,
"text", MC_PIECES, NULL));*/
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Downloaded", rend,
"text", MC_DOWN, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Uploaded", rend,
"text", MC_UP, NULL));
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
g_signal_connect(G_OBJECT(view), "button-press-event",
G_CALLBACK(listclick), data);
g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
gtk_widget_show_all(view);
return view;
}
gboolean
updatemodel(gpointer gdata) {
struct cbdata *data = gdata;
tr_stat_t *st;
int ii, max, prog;
GtkTreeIter iter;
max = tr_torrentStat(data->tr, &st);
for(ii = 0; ii < max; ii++) {
if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
gtk_list_store_append(data->model, &iter);
if(0.0 > (prog = st[ii].progress * 100.0))
prog = 0;
else if(100 < prog)
prog = 100;
/* XXX find out if setting the same data emits changed signal */
gtk_list_store_set(data->model, &iter, MC_ROW_INDEX, ii,
MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize, MC_STAT, st[ii].status,
MC_ERR, st[ii].error, MC_PROG, prog, MC_DRATE, st[ii].rateDownload,
MC_URATE, st[ii].rateUpload, MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
/*MC_PIECES, st[ii].pieces,*/ MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
}
free(st);
return TRUE;
}
gboolean
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
struct cbdata *data = gdata;
if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
dopopupmenu(widget, event, data);
return TRUE;
}
return FALSE;
}
gboolean
listpopup(GtkWidget *widget, gpointer userdata) {
dopopupmenu(widget, NULL, userdata);
return TRUE;
}
void
dopopupmenu(GtkWidget *widget SHUTUP, GdkEventButton *event,
struct cbdata *data) {
GtkWidget *menu = gtk_menu_new();
GtkWidget *item;
GtkTreePath *path;
GtkTreeIter iter;
unsigned int ii;
int index;
index = -1;
if(NULL != event && gtk_tree_view_get_path_at_pos(
data->view, event->x, event->y, &path, NULL, NULL, NULL)) {
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter, MC_ROW_INDEX, &index, -1);
gtk_tree_path_free(path);
}
/* XXX am I leaking references here? */
/* XXX can I cache this in cbdata? */
for(ii = 0; ii < sizeof(actionitems) / sizeof(actionitems[0]); ii++) {
item = gtk_menu_item_new_with_label(actionitems[ii].name);
g_object_set_data(G_OBJECT(item), LIST_ACTION,
GINT_TO_POINTER(actionitems[ii].act));
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
GINT_TO_POINTER(FROM_POPUP));
g_object_set_data(G_OBJECT(item), LIST_INDEX, GINT_TO_POINTER(index));
g_signal_connect(G_OBJECT(item), "activate",
G_CALLBACK(actionclick), data);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
}
gtk_widget_show_all(menu);
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
(NULL == event ? 0 : event->button),
gdk_event_get_time((GdkEvent*)event));
}
void
actionclick(GtkWidget *widget, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
GtkTreeModel *model;
GtkTreeIter iter;
enum listact act =
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION));
enum listfrom from =
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_ACTION_FROM));
int index;
tr_stat_t *sb;
if(ACT_OPEN == act) {
makeaddwind(data);
return;
}
index = -1;
switch(from) {
case FROM_BUTTON:
if(gtk_tree_selection_get_selected(sel, &model, &iter))
gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
MC_ROW_INDEX, &index, -1);
/* XXX should I assert(0 <= index) to insure a row was selected? */
break;
case FROM_POPUP:
index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), LIST_INDEX));
break;
default:
assert(!"unknown action source");
break;
}
if(0 <= index) {
switch(act) {
case ACT_START:
tr_torrentStart(data->tr, index);
savetorrents(data->tr, data->wind, -1, NULL);
break;
case ACT_STOP:
tr_torrentStop(data->tr, index);
savetorrents(data->tr, data->wind, -1, NULL);
break;
case ACT_DELETE:
/* XXX need to be able to stat just one torrent */
if(index >= tr_torrentStat(data->tr, &sb)) {
assert(!"XXX i'm tired");
}
if(TR_TORRENT_NEEDS_STOP(sb[index].status))
tr_torrentStop(data->tr, index);
free(sb);
tr_torrentClose(data->tr, index);
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
savetorrents(data->tr, data->wind, -1, NULL);
break;
case ACT_INFO:
makeinfowind(data, index);
break;
default:
assert(!"unknown type");
break;
}
}
}
void
makeaddwind(struct cbdata *data) {
GtkWidget *wind = gtk_file_selection_new("Add a Torrent");
g_object_set_data(G_OBJECT(GTK_FILE_SELECTION(wind)->ok_button),
CBDATA_PTR, data);
g_signal_connect(GTK_FILE_SELECTION(wind)->ok_button, "clicked",
G_CALLBACK(fileclick), wind);
g_signal_connect_swapped(GTK_FILE_SELECTION(wind)->cancel_button, "clicked",
G_CALLBACK(gtk_widget_destroy), wind);
gtk_window_set_transient_for(GTK_WINDOW(wind), GTK_WINDOW(data->wind));
gtk_window_set_destroy_with_parent(GTK_WINDOW(wind), TRUE);
gtk_window_set_modal(GTK_WINDOW(wind), TRUE);
gtk_widget_show_all(wind);
}
gboolean
addtorrent(tr_handle_t *tr, GtkWidget *parentwind, const char *torrent,
const char *dir, gboolean paused) {
char *wd;
if(0 != tr_torrentInit(tr, torrent)) {
/* XXX would be nice to have errno strings, are they printed to stdout? */
errmsg(parentwind, "Failed to open torrent file %s", torrent);
return FALSE;
}
if(NULL != dir)
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
else {
/* XXX need pref for download directory */
wd = g_new(char, MAXPATHLEN + 1);
if(NULL == getcwd(wd, MAXPATHLEN + 1))
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
else {
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
free(wd);
}
}
if(!paused)
tr_torrentStart(tr, tr_torrentCount(tr) - 1);
return TRUE;
}
void
fileclick(GtkWidget *widget, gpointer gdata) {
struct cbdata *data = g_object_get_data(G_OBJECT(widget), CBDATA_PTR);
GtkWidget *wind = gdata;
const char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(wind));
if(addtorrent(data->tr, data->wind, file, NULL, FALSE)) {
updatemodel(data);
savetorrents(data->tr, data->wind, -1, NULL);
}
gtk_widget_destroy(wind);
}
const char *
statusstr(int status) {
switch(status) {
case TR_STATUS_CHECK: return "check";
case TR_STATUS_DOWNLOAD: return "download";
case TR_STATUS_SEED: return "seed";
case TR_STATUS_STOPPING: return "stopping";
case TR_STATUS_STOPPED: return "stopped";
case TR_STATUS_PAUSE: return "pause";
case TR_TRACKER_ERROR: return "error";
default:
assert(!"unknown status code");
return NULL;
}
}
void
makeinfowind(struct cbdata *data, int index) {
tr_stat_t *sb;
GtkWidget *wind, *table, *name, *value;
char buf[32];
if(index >= tr_torrentStat(data->tr, &sb)) {
assert(!"XXX i'm tired");
}
wind = gtk_dialog_new_with_buttons(sb[index].info.name, GTK_WINDOW(data->wind),
GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
table = gtk_table_new(21, 2, FALSE);
name = gtk_label_new("Torrent File");
value = gtk_label_new(sb[index].info.torrent);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 0, 1);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 0, 1);
name = gtk_label_new("Name");
value = gtk_label_new(sb[index].info.name);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 1, 2);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 1, 2);
name = gtk_label_new("Tracker Address");
value = gtk_label_new(sb[index].info.trackerAddress);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 2, 3);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 2, 3);
name = gtk_label_new("Tracker Port");
snprintf(buf, sizeof buf, "%i", sb[index].info.trackerPort);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 3, 4);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 3, 4);
name = gtk_label_new("Tracker Announce URL");
value = gtk_label_new(sb[index].info.trackerAnnounce);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 4, 5);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 4, 5);
name = gtk_label_new("Piece Size");
snprintf(buf, sizeof buf, "%i", sb[index].info.pieceSize);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 5, 6);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 5, 6);
name = gtk_label_new("Piece Count");
snprintf(buf, sizeof buf, "%i", sb[index].info.pieceCount);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 6, 7);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 6, 7);
name = gtk_label_new("Total Size");
snprintf(buf, sizeof buf, "%llu", sb[index].info.totalSize);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 7, 8);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 7, 8);
name = gtk_label_new("File Count");
snprintf(buf, sizeof buf, "%i", sb[index].info.fileCount);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 8, 9);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 8, 9);
name = gtk_label_new("Status");
value = gtk_label_new(statusstr(sb[index].status));
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 9, 10);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 9, 10);
name = gtk_label_new("Error");
value = gtk_label_new(sb[index].error);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 10, 11);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 10, 11);
name = gtk_label_new("Progress");
snprintf(buf, sizeof buf, "%f", sb[index].progress);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 11, 12);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 11, 12);
name = gtk_label_new("Download Rate");
value = gtk_label_new(buf);
snprintf(buf, sizeof buf, "%f", sb[index].rateDownload);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 12, 13);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 12, 13);
name = gtk_label_new("Upload Rate");
snprintf(buf, sizeof buf, "%f", sb[index].rateUpload);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 13, 14);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 13, 14);
name = gtk_label_new("ETA");
snprintf(buf, sizeof buf, "%i", sb[index].eta);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 14, 15);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 14, 15);
name = gtk_label_new("Total Peers");
snprintf(buf, sizeof buf, "%i", sb[index].peersTotal);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 15, 16);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 15, 16);
name = gtk_label_new("Uploading Peers");
snprintf(buf, sizeof buf, "%i", sb[index].peersUploading);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 16, 17);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 16, 17);
name = gtk_label_new("Downloading Peers");
snprintf(buf, sizeof buf, "%i", sb[index].peersDownloading);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 17, 18);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 17, 18);
name = gtk_label_new("Downloaded");
snprintf(buf, sizeof buf, "%llu", sb[index].downloaded);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 18, 19);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 18, 19);
name = gtk_label_new("Uploaded");
snprintf(buf, sizeof buf, "%llu", sb[index].uploaded);
value = gtk_label_new(buf);
gtk_table_attach_defaults(GTK_TABLE(table), name, 0, 1, 19, 20);
gtk_table_attach_defaults(GTK_TABLE(table), value, 1, 2, 19, 20);
gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(wind)->vbox), table);
g_signal_connect(G_OBJECT(wind), "response",
G_CALLBACK(gtk_widget_destroy), NULL);
gtk_widget_show_all(wind);
free(sb);
}
gboolean
savetorrents(tr_handle_t *tr, GtkWidget *wind, int count, tr_stat_t *stat) {
char *errstr;
tr_stat_t *st;
gboolean ret;
assert(NULL != tr || 0 <= count);
if(0 <= count)
ret = cf_savestate(count, stat, &errstr);
else {
count = tr_torrentStat(tr, &st);
ret = cf_savestate(count, st, &errstr);
free(st);
}
if(!ret) {
errmsg(wind, "%s", errstr);
g_free(errstr);
}
return ret;
}

121
gtk/util.c Normal file
View File

@@ -0,0 +1,121 @@
/*
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdarg.h>
#include <gtk/gtk.h>
#include "util.h"
static void
errcb(GtkWidget *widget, int resp, gpointer data);
gboolean
strbool(const char *str) {
switch(str[0]) {
case 'y':
case 'Y':
case '1':
case 'j':
case 'e':
return TRUE;
default:
if(0 == g_ascii_strcasecmp("on", str))
return TRUE;
break;
}
return FALSE;
}
GtkWidget *
errmsg(GtkWidget *wind, const char *format, ...) {
GtkWidget *dialog;
va_list ap;
va_start(ap, format);
dialog = verrmsg(wind, NULL, NULL, format, ap);
va_end(ap);
return dialog;
}
GtkWidget *
errmsg_full(GtkWidget *wind, errfunc_t func, void *data,
const char *format, ...) {
GtkWidget *dialog;
va_list ap;
va_start(ap, format);
dialog = verrmsg(wind, func, data, format, ap);
va_end(ap);
return dialog;
}
GtkWidget *
verrmsg(GtkWidget *wind, errfunc_t func, void *data,
const char *format, va_list ap) {
GtkWidget *dialog;
char *msg;
GList *funcdata;
msg = g_strdup_vprintf(format, ap);
if(NULL == wind)
dialog = gtk_message_dialog_new(
NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
else
dialog = gtk_message_dialog_new(
GTK_WINDOW(wind), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
if(NULL == func)
funcdata = NULL;
else
funcdata = g_list_append(g_list_append(NULL, func), data);
g_signal_connect(dialog, "response", G_CALLBACK(errcb), funcdata);
if(NULL != wind)
gtk_widget_show_all(dialog);
g_free(msg);
return dialog;
}
static void
errcb(GtkWidget *widget, int resp SHUTUP, gpointer data) {
GList *funcdata;
errfunc_t func;
if(NULL != data) {
funcdata = g_list_first(data);
func = funcdata->data;
data = funcdata->next->data;
func(data);
g_list_free(funcdata);
}
gtk_widget_destroy(widget);
}

65
gtk/util.h Normal file
View File

@@ -0,0 +1,65 @@
/*
Copyright (c) 2005 Joshua Elsasser. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TG_UTIL_H
#define TG_UTIL_H
#include <stdarg.h>
/* macro to shut up "unused parameter" warnings */
#ifdef __GNUC__
#define SHUTUP __attribute__((unused))
#else
#define SHUTUP
#endif
gboolean
strbool(const char *str);
typedef void (*errfunc_t)(void*);
/* if wind is NULL then you must call gtk_widget_show on the returned widget */
GtkWidget *
errmsg(GtkWidget *wind, const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 2, 3)))
#endif
;
GtkWidget *
errmsg_full(GtkWidget *wind, errfunc_t func, void *data,
const char *format, ...)
#ifdef __GNUC__
__attribute__ ((format (printf, 4, 5)))
#endif
;
GtkWidget *
verrmsg(GtkWidget *wind, errfunc_t func, void *data,
const char *format, va_list ap);
#endif /* TG_UTIL_H */