Merge from branches/new_api:r161

This commit is contained in:
Eric Petit
2006-03-23 12:39:39 +00:00
parent 9321447c68
commit a1830a25bf
33 changed files with 1915 additions and 1627 deletions

View File

@@ -414,16 +414,18 @@ getstateval(struct cf_torrentstate *state, char *line) {
}
gboolean
cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
cf_savestate(GList *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;
int fd;
char *torrentfile, *torrentdir, *line;
gsize written;
gboolean paused;
GIOStatus res;
tr_stat_t *sb;
tr_info_t *in;
*errstr = NULL;
@@ -444,11 +446,12 @@ cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
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 | TR_STATUS_PAUSE) & torrents[ii].status);
torrentfile = g_strescape(torrents[ii].info.torrent, "");
torrentdir = g_strescape(torrents[ii].folder, "");
while(NULL != torrents) {
sb = tr_torrentStat(torrents->data);
in = tr_torrentInfo(torrents->data);
paused = (TR_STATUS_INACTIVE & sb->status);
torrentfile = g_strescape(in->torrent, "");
torrentdir = g_strescape(tr_torrentGetFolder(torrents->data), "");
/* g_strcompress */
line = g_strdup_printf("torrent=\"%s\" dir=\"%s\" paused=\"%s\"%c",
torrentfile, torrentdir, (paused ? "yes" : "no"),
@@ -466,6 +469,7 @@ cf_savestate(int count, tr_stat_t *torrents, char **errstr) {
assert(!"unknown return code");
goto done;
}
torrents = torrents->next;
}
if(NULL != err ||
G_IO_STATUS_ERROR == g_io_channel_shutdown(io, TRUE, &err)) {

View File

@@ -50,7 +50,7 @@ cf_saveprefs(char **errstr);
GList *
cf_loadstate(char **errstr);
gboolean
cf_savestate(int count, tr_stat_t *torrents, char **errstr);
cf_savestate(GList *torrents, char **errstr);
void
cf_freestate(struct cf_torrentstate *state);

View File

@@ -49,9 +49,8 @@ struct prefdata {
struct addcb {
add_torrent_func_t addfunc;
GtkWindow *parent;
tr_handle_t *tr;
torrents_added_func_t donefunc;
void *donedata;
void *data;
gboolean autostart;
gboolean usingaltdir;
GtkFileChooser *altdir;
@@ -219,6 +218,7 @@ clickdialog(GtkWidget *widget, int resp, gpointer gdata) {
g_free(errstr);
}
/* XXX would be nice to have errno strings, are they printed to stdout? */
tr_setBindPort(data->tr, gtk_spin_button_get_value_as_int(data->port));
setlimit(data->tr);
}
@@ -240,8 +240,8 @@ setlimit(tr_handle_t *tr) {
}
void
makeaddwind(add_torrent_func_t addfunc, GtkWindow *parent, tr_handle_t *tr,
torrents_added_func_t donefunc, void *donedata) {
makeaddwind(GtkWindow *parent, add_torrent_func_t addfunc,
torrents_added_func_t donefunc, void *cbdata) {
GtkWidget *wind = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent,
GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
@@ -260,9 +260,8 @@ makeaddwind(add_torrent_func_t addfunc, GtkWindow *parent, tr_handle_t *tr,
data->addfunc = addfunc;
data->parent = parent;
data->tr = tr;
data->donefunc = donefunc;
data->donedata = donedata;
data->data = cbdata;
data->autostart = TRUE;
data->usingaltdir = FALSE;
data->altdir = GTK_FILE_CHOOSER(getdir);
@@ -326,11 +325,12 @@ addresp(GtkWidget *widget, gint resp, gpointer gdata) {
dir = gtk_file_chooser_get_filename(data->altdir);
files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget));
for(ii = files; NULL != ii; ii = ii->next)
if(data->addfunc(data->tr, data->parent, ii->data, dir,
if(data->addfunc(data->data, ii->data, dir,
/* XXX need to group errors here */
!data->autostart, NULL))
added = TRUE;
if(added)
data->donefunc(data->donedata);
data->donefunc(data->data);
if(NULL != dir)
g_free(dir);
}
@@ -375,18 +375,18 @@ addresp(GtkWidget *widget, gint resp, gpointer gdata) {
} while(0)
void
makeinfowind(GtkWindow *parent, tr_handle_t *tr, int id) {
makeinfowind(GtkWindow *parent, tr_torrent_t *tor) {
tr_stat_t *sb;
tr_info_t *in;
GtkWidget *wind, *label;
int ii;
char *str;
const int rowcount = 14;
GtkWidget *table = gtk_table_new(rowcount, 2, FALSE);
/* XXX would be nice to be able to stat just one */
if(id >= tr_torrentStat(tr, &sb))
assert(!"XXX ");
str = g_strdup_printf(_("%s Properties"), sb[id].info.name);
sb = tr_torrentStat(tor);
in = tr_torrentInfo(tor);
str = g_strdup_printf(_("%s Properties"), in->name);
wind = gtk_dialog_new_with_buttons(str, parent,
GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
@@ -401,7 +401,7 @@ makeinfowind(GtkWindow *parent, tr_handle_t *tr, int id) {
label = gtk_label_new(NULL);
gtk_label_set_selectable(GTK_LABEL(label), TRUE);
str = g_markup_printf_escaped("<big>%s</big>", sb[id].info.name);
str = g_markup_printf_escaped("<big>%s</big>", in->name);
gtk_label_set_markup(GTK_LABEL(label), str);
g_free(str);
gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 2, 0, 1);
@@ -410,30 +410,30 @@ makeinfowind(GtkWindow *parent, tr_handle_t *tr, int id) {
INFOSEP(table, ii);
if(80 == sb[id].info.trackerPort)
if(80 == in->trackerPort)
INFOLINEA(table, ii, _("Tracker:"), g_strdup_printf("http://%s",
sb[id].info.trackerAddress));
in->trackerAddress));
else
INFOLINEA(table, ii, _("Tracker:"), g_strdup_printf("http://%s:%i",
sb[id].info.trackerAddress, sb[id].info.trackerPort));
INFOLINE(table, ii, _("Announce:"), sb[id].info.trackerAnnounce);
INFOLINEA(table, ii, _("Piece Size:"), readablesize(sb[id].info.pieceSize));
INFOLINEF(table, ii, "%i", _("Pieces:"), sb[id].info.pieceCount);
INFOLINEA(table, ii, _("Total Size:"), readablesize(sb[id].info.totalSize));
if(0 > sb[id].seeders)
in->trackerAddress, in->trackerPort));
INFOLINE(table, ii, _("Announce:"), in->trackerAnnounce);
INFOLINEA(table, ii, _("Piece Size:"), readablesize(in->pieceSize));
INFOLINEF(table, ii, "%i", _("Pieces:"), in->pieceCount);
INFOLINEA(table, ii, _("Total Size:"), readablesize(in->totalSize));
if(0 > sb->seeders)
INFOLINE(table, ii, _("Seeders:"), _("?"));
else
INFOLINEF(table, ii, "%i", _("Seeders:"), sb[id].seeders);
if(0 > sb[id].leechers)
INFOLINEF(table, ii, "%i", _("Seeders:"), sb->seeders);
if(0 > sb->leechers)
INFOLINE(table, ii, _("Leechers:"), _("?"));
else
INFOLINEF(table, ii, "%i", _("Leechers:"), sb[id].leechers);
INFOLINEF(table, ii, "%i", _("Leechers:"), sb->leechers);
INFOSEP(table, ii);
INFOLINE(table, ii, _("Directory:"), sb[id].folder);
INFOLINEA(table, ii, _("Downloaded:"), readablesize(sb[id].downloaded));
INFOLINEA(table, ii, _("Uploaded:"), readablesize(sb[id].uploaded));
INFOLINE(table, ii, _("Directory:"), tr_torrentGetFolder(tor));
INFOLINEA(table, ii, _("Downloaded:"), readablesize(sb->downloaded));
INFOLINEA(table, ii, _("Uploaded:"), readablesize(sb->uploaded));
INFOSEP(table, ii);
@@ -443,5 +443,4 @@ makeinfowind(GtkWindow *parent, tr_handle_t *tr, int id) {
g_signal_connect(G_OBJECT(wind), "response",
G_CALLBACK(gtk_widget_destroy), NULL);
gtk_widget_show_all(wind);
free(sb);
}

View File

@@ -37,7 +37,7 @@
#define DEFAULT_UPLIMIT 20
typedef gboolean (*add_torrent_func_t)(tr_handle_t*, GtkWindow*, const char*, const char *, gboolean, GList **);
typedef gboolean (*add_torrent_func_t)(void *, const char *, const char *, gboolean, GList **);
typedef void (*torrents_added_func_t)(void *);
void
@@ -49,11 +49,11 @@ setlimit(tr_handle_t *tr);
/* show the "add a torrent" dialog */
void
makeaddwind(add_torrent_func_t addfunc, GtkWindow *parent, tr_handle_t *tr,
torrents_added_func_t donefunc, void *donedata);
makeaddwind(GtkWindow *parent, add_torrent_func_t addfunc,
torrents_added_func_t donefunc, void *cbdata);
/* show the info window for a torrent */
void
makeinfowind(GtkWindow *parent, tr_handle_t *tr, int index);
makeinfowind(GtkWindow *parent, tr_torrent_t *tor);
#endif /* TG_PREFS_H */

View File

@@ -27,6 +27,7 @@
#include <sys/param.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
@@ -50,7 +51,7 @@
struct cbdata {
tr_handle_t *tr;
GtkWindow *wind;
GtkListStore *model;
GtkTreeModel *model;
GtkTreeView *view;
GtkStatusbar *bar;
GtkWidget **buttons;
@@ -77,21 +78,21 @@ tr_pieces_free(gpointer);
void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved);
GtkWidget *
makewind_toolbar(struct cbdata *data);
GtkWidget *
makewind_list(struct cbdata *data);
gboolean
winclose(GtkWidget *widget, GdkEvent *event, gpointer gdata);
gboolean
exitcheck(gpointer gdata);
void
stoptransmission(void *tr);
stoptransmission(tr_handle_t *tr);
void
setupdrag(GtkWidget *widget, struct cbdata *data);
void
gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
GtkSelectionData *sel, guint info, guint time, gpointer gdata);
GtkWidget *
makewind_toolbar(struct cbdata *data);
GtkWidget *
makewind_list(struct cbdata *data);
static void
stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata);
void
@@ -113,9 +114,11 @@ void
dopopupmenu(GdkEventButton *event, struct cbdata *data,
GList *ids, int status);
void
killmenu(GtkWidget *menu, gpointer *gdata SHUTUP);
killmenu(GtkWidget *menu, gpointer *gdata);
void
actionclick(GtkWidget *widget, gpointer gdata);
void
findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter);
gint
intrevcmp(gconstpointer a, gconstpointer b);
void
@@ -123,18 +126,24 @@ doubleclick(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col,
gpointer gdata);
gboolean
addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
const char *dir, gboolean paused, GList **errs);
addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
GList **errs);
void
addedtorrents(void *vdata);
gboolean
savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat);
savetorrents(tr_handle_t *tr, GtkWindow *wind);
void
orstatus(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
gpointer gdata);
void
makeidlist(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
gpointer gdata);
void
maketorrentlist(tr_torrent_t *tor, void *data);
void
setupsighandlers(void);
void
fatalsig(int sig);
#define TR_TYPE_PIECES_NAME "tr-type-pieces"
#define TR_TYPE_PIECES ((const GType)tr_type_pieces)
@@ -146,7 +155,7 @@ enum listact { ACT_OPEN, ACT_START, ACT_STOP, ACT_DELETE, ACT_INFO, ACT_PREF };
#define LIST_ACTION_FROM "torrent-list-action-from"
enum listfrom { FROM_BUTTON, FROM_POPUP };
#define LIST_INDEX "torrent-list-indexes"
#define LIST_IDS "torrent-list-ids"
#define LIST_MENU_WIDGET "torrent-list-popup-menu-widget"
struct { const gchar *name; const gchar *id; enum listact act; gboolean nomenu;
@@ -155,10 +164,10 @@ actionitems[] = {
{N_("Add"), GTK_STOCK_ADD, ACT_OPEN, FALSE, 0,
N_("Add a new torrent"), "XXX"},
{N_("Start"), GTK_STOCK_EXECUTE, ACT_START, FALSE,
(TR_STATUS_STOPPING | TR_STATUS_PAUSE),
TR_STATUS_INACTIVE,
N_("Start a torrent that is not running"), "XXX"},
{N_("Stop"), GTK_STOCK_STOP, ACT_STOP, FALSE,
~(TR_STATUS_STOPPING | TR_STATUS_PAUSE),
TR_STATUS_ACTIVE,
N_("Stop a torrent that is running"), "XXX"},
{N_("Remove"), GTK_STOCK_REMOVE, ACT_DELETE, FALSE, ~0,
N_("Remove a torrent"), "XXX"},
@@ -169,6 +178,12 @@ actionitems[] = {
};
#define CBDATA_PTR "callback-data-pointer"
#define SIGCOUNT_MAX 3
static sig_atomic_t global_sigcount = 0;
static int global_lastsig = 0;
int
main(int argc, char **argv) {
GtkWidget *mainwind, *preferr, *stateerr;
@@ -178,6 +193,8 @@ main(int argc, char **argv) {
const char *pref;
long intval;
setupsighandlers();
gtk_init(&argc, &argv);
bindtextdomain("transmission-gtk", LOCALEDIR);
@@ -187,8 +204,6 @@ main(int argc, char **argv) {
tr = tr_init();
setuphandlers(stoptransmission, tr);
gtk_rc_parse_string(
"style \"transmission-standard\" {\n"
" GtkDialog::action-area-border = 6\n"
@@ -305,8 +320,8 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
loaderrs = NULL;
for(ii = g_list_first(saved); NULL != ii; ii = ii->next) {
ts = ii->data;
addtorrent(tr, GTK_WINDOW(wind), ts->ts_torrent, ts->ts_directory,
ts->ts_paused, &loaderrs);
addtorrent(data, ts->ts_torrent, ts->ts_directory, ts->ts_paused,
&loaderrs);
cf_freestate(ts);
}
g_list_free(saved);
@@ -319,7 +334,7 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
g_list_foreach(loaderrs, (GFunc)g_free, NULL);
g_list_free(loaderrs);
g_free(str);
savetorrents(tr, GTK_WINDOW(wind), -1, NULL);
savetorrents(tr, GTK_WINDOW(wind));
}
data->timer = g_timeout_add(500, updatemodel, data);
@@ -340,35 +355,126 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
gtk_widget_show(wind);
}
/* 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)
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_show_arrow(GTK_TOOLBAR(bar), FALSE);
gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
data->buttons = g_new(GtkWidget*, ALEN(actionitems));
for(ii = 0; ii < ALEN(actionitems); ii++) {
item = gtk_tool_button_new_from_stock(actionitems[ii].id);
data->buttons[ii] = GTK_WIDGET(item);
gtk_tool_button_set_label(GTK_TOOL_BUTTON(item),
gettext(actionitems[ii].name));
gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
gettext(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), item, -1);
}
return bar;
}
/* XXX check for unused data in model */
enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_TERR, MC_PROG, MC_DRATE, MC_URATE,
MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
MC_TORRENT, MC_ROW_COUNT};
GtkWidget *
makewind_list(struct cbdata *data) {
GType types[] = {
/* info->name, info->totalSize, status, error, trackerError, */
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING,
/* progress, rateDownload, rateUpload, eta, peersTotal, */
G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT,
/* peersUploading, peersDownloading, pieces, downloaded, */
G_TYPE_INT, G_TYPE_INT, TR_TYPE_PIECES, G_TYPE_UINT64,
/* uploaded, the handle for the torrent */
G_TYPE_UINT64, G_TYPE_POINTER};
GtkListStore *store;
GtkWidget *view;
GtkTreeViewColumn *col;
GtkTreeSelection *sel;
GtkCellRenderer *namerend, *progrend;
char *str;
assert(MC_ROW_COUNT == ALEN(types));
store = gtk_list_store_newv(MC_ROW_COUNT, types);
view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
/* XXX do I need to worry about reference counts anywhere else? */
g_object_unref(G_OBJECT(store));
data->model = GTK_TREE_MODEL(store);
data->view = GTK_TREE_VIEW(view);
namerend = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new_with_attributes(_("Name"), namerend, NULL);
gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
gtk_tree_view_column_set_expand(col, TRUE);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
progrend = tr_cell_renderer_torrent_new();
/* this string is only used to determing the size of the progress bar */
str = g_markup_printf_escaped("<big>%s</big>", _(" fnord fnord "));
g_object_set(progrend, "label", str, NULL);
g_free(str);
col = gtk_tree_view_column_new_with_attributes(_("Progress"), progrend, NULL);
gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
/* XXX this shouldn't be necessary */
g_signal_connect(view, "notify", G_CALLBACK(stylekludge), progrend);
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_MULTIPLE);
g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
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);
g_signal_connect(G_OBJECT(view), "row-activated",
G_CALLBACK(doubleclick), data);
gtk_widget_show_all(view);
return view;
}
gboolean
winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
struct cbdata *data = gdata;
struct exitdata *edata;
tr_stat_t *st;
int ii;
GtkTreeIter iter;
tr_torrent_t *tor;
gboolean going;
if(0 >= data->timer)
g_source_remove(data->timer);
data->timer = -1;
blocksigs();
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);
going = gtk_tree_model_get_iter_first(data->model, &iter);
while(going) {
gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
st = tr_torrentStat(tor);
if(TR_STATUS_ACTIVE & st->status) {
tr_torrentStop(tor);
going = gtk_tree_model_iter_next(data->model, &iter);
} else {
/*fprintf(stderr, "quit: closing %i %s\n", ii, st[ii-1].info.name);*/
tr_torrentClose(data->tr, ii - 1);
tr_torrentClose(data->tr, tor);
going = gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
}
}
free(st);
unblocksigs();
/* XXX should disable widgets or something */
@@ -378,8 +484,6 @@ winclose(GtkWidget *widget SHUTUP, GdkEvent *event SHUTUP, gpointer gdata) {
edata->started = time(NULL);
edata->timer = g_timeout_add(500, exitcheck, edata);
/*fprintf(stderr, "quit: starting timeout at %i\n", edata->started);*/
/* returning FALSE means to destroy the window */
return TRUE;
}
@@ -388,25 +492,27 @@ gboolean
exitcheck(gpointer gdata) {
struct exitdata *data = gdata;
tr_stat_t *st;
int ii;
GtkTreeIter iter;
tr_torrent_t *tor;
gboolean go;
blocksigs();
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);
go = gtk_tree_model_get_iter_first(data->cbdata->model, &iter);
while(go) {
gtk_tree_model_get(data->cbdata->model, &iter, MC_TORRENT, &tor, -1);
st = tr_torrentStat(tor);
if(!(TR_STATUS_PAUSE & st->status))
go = gtk_tree_model_iter_next(data->cbdata->model, &iter);
else {
tr_torrentClose(data->cbdata->tr, tor);
go = gtk_list_store_remove(GTK_LIST_STORE(data->cbdata->model), &iter);
}
}
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) {
assert(gtk_tree_model_get_iter_first(data->cbdata->model, &iter));
updatemodel(data->cbdata);
unblocksigs();
return TRUE;
}
@@ -416,11 +522,7 @@ exitcheck(gpointer gdata) {
g_source_remove(data->timer);
data->timer = -1;
/*fprintf(stderr, "quit: giving up on %i torrents\n",
tr_torrentCount(data->cbdata->tr));*/
stoptransmission(data->cbdata->tr);
clearhandlers();
unblocksigs();
gtk_widget_destroy(GTK_WIDGET(data->cbdata->wind));
g_free(data->cbdata);
@@ -431,9 +533,15 @@ exitcheck(gpointer gdata) {
}
void
stoptransmission(void *tr) {
while(0 < tr_torrentCount(tr))
tr_torrentClose(tr, 0);
stoptransmission(tr_handle_t *tr) {
GList *list, *ii;
list = NULL;
tr_torrentIterate(tr, maketorrentlist, &list);
for(ii = g_list_first(list); NULL != ii; ii = ii->next)
tr_torrentClose(tr, ii->data);
g_list_free(list);
tr_close(tr);
}
@@ -500,7 +608,7 @@ gotdrag(GtkWidget *widget SHUTUP, GdkDragContext *dc, gint x SHUTUP,
0 == g_stat(hostless, &sb))
deslashed = hostless;
/* finally, try to add it as a torrent */
if(addtorrent(data->tr, data->wind, deslashed, NULL, FALSE, &errs))
if(addtorrent(data, deslashed, NULL, FALSE, &errs))
gotfile = TRUE;
}
}
@@ -541,99 +649,6 @@ setupdrag(GtkWidget *widget, struct cbdata *data) {
ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
}
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_show_arrow(GTK_TOOLBAR(bar), FALSE);
gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
data->buttons = g_new(GtkWidget*, ALEN(actionitems));
for(ii = 0; ii < ALEN(actionitems); ii++) {
item = gtk_tool_button_new_from_stock(actionitems[ii].id);
data->buttons[ii] = GTK_WIDGET(item);
gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), gettext(actionitems[ii].name));
gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
gettext(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), item, -1);
}
return bar;
}
/* XXX check for unused data in model */
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_FLOAT,
/* 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 *namerend, *progrend;
char *str;
assert(MC_ROW_COUNT == ALEN(types));
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);
namerend = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new_with_attributes(_("Name"), namerend, NULL);
gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
gtk_tree_view_column_set_expand(col, TRUE);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
progrend = tr_cell_renderer_torrent_new();
/* this string is only used to determing the size of the progress bar */
str = g_markup_printf_escaped("<big>%s</big>", _(" fnord fnord "));
g_object_set(progrend, "label", str, NULL);
g_free(str);
col = gtk_tree_view_column_new_with_attributes(_("Progress"), progrend, NULL);
gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
/* XXX this shouldn't be necessary */
g_signal_connect(view, "notify", G_CALLBACK(stylekludge), progrend);
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_MULTIPLE);
g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
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);
g_signal_connect(G_OBJECT(view), "row-activated",
G_CALLBACK(doubleclick), data);
gtk_widget_show_all(view);
return view;
}
/* kludge to have the progress bars notice theme changes */
static void
stylekludge(GObject *obj, GParamSpec *spec, gpointer gdata) {
@@ -666,15 +681,14 @@ fixbuttons(GtkTreeSelection *sel, gpointer gdata) {
void
dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
char *name, *mb, *err, *str, *top, *bottom;
char *name, *mb, *terr, *str, *top, *bottom;
guint64 size;
gfloat prog;
int status, eta, tpeers, upeers, dpeers;
int status, err, eta, tpeers, upeers, dpeers;
/* XXX should I worry about gtk_tree_model_get failing? */
gtk_tree_model_get(model, iter, MC_NAME, &name, MC_STAT, &status,
MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta, MC_PEERS, &tpeers,
MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
MC_ERR, &err, MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta,
MC_PEERS, &tpeers, MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
if(0 > tpeers)
tpeers = 0;
@@ -707,10 +721,10 @@ dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
assert("XXX unknown status");
}
if(status & TR_TRACKER_ERROR) {
gtk_tree_model_get(model, iter, MC_ERR, &err, -1);
bottom = g_strconcat(_("Error: "), err, NULL);
g_free(err);
if(TR_NOERROR != err) {
gtk_tree_model_get(model, iter, MC_TERR, &terr, -1);
bottom = g_strconcat(_("Error: "), terr, NULL);
g_free(terr);
}
else if(status & TR_STATUS_DOWNLOAD)
bottom = g_strdup_printf(ngettext("Downloading from %i of %i peer",
@@ -736,7 +750,6 @@ dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
gfloat prog, dl, ul;
guint64 down, up;
/* XXX should I worry about gtk_tree_model_get failing? */
gtk_tree_model_get(model, iter, MC_PROG, &prog, MC_DRATE, &dl, MC_URATE, &ul,
MC_DOWN, &down, MC_UP, &up, -1);
if(0.0 > prog)
@@ -763,36 +776,34 @@ dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
gboolean
updatemodel(gpointer gdata) {
struct cbdata *data = gdata;
tr_torrent_t *tor;
tr_stat_t *st;
int ii, max;
tr_info_t *in;
GtkTreeIter iter;
float up, down;
char *upstr, *downstr, *str;
blocksigs();
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);
/* 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, st[ii].progress,
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_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
if(0 < global_sigcount) {
stoptransmission(data->tr);
global_sigcount = SIGCOUNT_MAX;
raise(global_lastsig);
}
free(st);
/* remove any excess rows */
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))
while(gtk_list_store_remove(data->model, &iter))
;
if(gtk_tree_model_get_iter_first(data->model, &iter)) {
do {
gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
st = tr_torrentStat(tor);
in = tr_torrentInfo(tor);
/* XXX find out if setting the same data emits changed signal */
gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_NAME, in->name,
MC_SIZE, in->totalSize, MC_STAT, st->status, MC_ERR, st->error,
MC_TERR, st->trackerError, MC_PROG, st->progress,
MC_DRATE, st->rateDownload, MC_URATE, st->rateUpload, MC_ETA, st->eta,
MC_PEERS, st->peersTotal, MC_UPEERS, st->peersUploading,
MC_DPEERS, st->peersDownloading, MC_DOWN, st->downloaded,
MC_UP, st->uploaded, -1);
} while(gtk_tree_model_iter_next(data->model, &iter));
}
/* update the status bar */
tr_torrentRates(data->tr, &up, &down);
@@ -809,8 +820,6 @@ updatemodel(gpointer gdata) {
/* the status of the selected item may have changed, so update the buttons */
fixbuttons(NULL, data);
unblocksigs();
return TRUE;
}
@@ -821,7 +830,8 @@ listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
GtkTreePath *path;
GtkTreeIter iter;
int index, status;
int status;
gpointer tor;
GList *ids;
if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
@@ -831,19 +841,18 @@ listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
/* no row was clicked, do the popup with no torrent IDs or status */
dopopupmenu(event, data, NULL, 0);
else {
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path)) {
if(gtk_tree_model_get_iter(data->model, &iter, path)) {
/* get ID and status for the right-clicked row */
gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter,
MC_ROW_INDEX, &index, MC_STAT, &status, -1);
gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor,
MC_STAT, &status, -1);
/* get a list of selected IDs */
ids = NULL;
gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
/* is the clicked row selected? */
if(NULL == g_list_find(ids, GINT_TO_POINTER(index))) {
if(NULL == g_list_find(ids, tor)) {
/* no, do the popup for just the clicked row */
g_list_free(ids);
dopopupmenu(event, data, g_list_append(NULL, GINT_TO_POINTER(index)),
status);
dopopupmenu(event, data, g_list_append(NULL, tor), status);
} else {
/* yes, do the popup for all the selected rows */
gtk_tree_selection_selected_foreach(sel, orstatus, &status);
@@ -862,14 +871,12 @@ gboolean
listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
GtkTreeModel *model;
GList *ids;
int status;
if(0 >= gtk_tree_selection_count_selected_rows(sel))
dopopupmenu(NULL, data, NULL, 0);
else {
assert(model == GTK_TREE_MODEL(data->model));
status = 0;
gtk_tree_selection_selected_foreach(sel, orstatus, &status);
ids = NULL;
@@ -900,7 +907,7 @@ dopopupmenu(GdkEventButton *event, struct cbdata *data,
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
GINT_TO_POINTER(FROM_POPUP));
/* set a glist of selected torrent's IDs */
g_object_set_data(G_OBJECT(item), LIST_INDEX, ids);
g_object_set_data(G_OBJECT(item), LIST_IDS, ids);
/* set the menu widget, so the activate handler can destroy it */
g_object_set_data(G_OBJECT(item), LIST_MENU_WIDGET, menu);
g_signal_connect(G_OBJECT(item), "activate",
@@ -909,7 +916,7 @@ dopopupmenu(GdkEventButton *event, struct cbdata *data,
}
/* set up the glist to be freed when the menu is destroyed */
g_object_set_data_full(G_OBJECT(menu), LIST_INDEX, ids,
g_object_set_data_full(G_OBJECT(menu), LIST_IDS, ids,
(GDestroyNotify)g_list_free);
/* destroy the menu if the user doesn't select anything */
@@ -935,11 +942,12 @@ actionclick(GtkWidget *widget, gpointer gdata) {
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, count;
unsigned int actindex;
tr_stat_t *sb;
GList *ids, *ii;
tr_torrent_t *tor;
gboolean updatesave;
GtkTreeIter iter;
/* destroy the popup menu, if any */
if(FROM_POPUP == from)
@@ -947,7 +955,7 @@ actionclick(GtkWidget *widget, gpointer gdata) {
switch(act) {
case ACT_OPEN:
makeaddwind(addtorrent, data->wind, data->tr, addedtorrents, data);
makeaddwind(data->wind, addtorrent, addedtorrents, data);
return;
case ACT_PREF:
if(!data->prefsopen)
@@ -961,10 +969,9 @@ actionclick(GtkWidget *widget, gpointer gdata) {
case FROM_BUTTON:
ids = NULL;
gtk_tree_selection_selected_foreach(sel, makeidlist, &ids);
/* XXX should I assert(0 <= index) to insure a row was selected? */
break;
case FROM_POPUP:
ids = g_object_get_data(G_OBJECT(widget), LIST_INDEX);
ids = g_object_get_data(G_OBJECT(widget), LIST_IDS);
break;
default:
assert(!"unknown action source");
@@ -976,60 +983,71 @@ actionclick(GtkWidget *widget, gpointer gdata) {
break;
assert(actindex < ALEN(actionitems));
blocksigs();
updatesave = FALSE;
count = tr_torrentStat(data->tr, &sb);
for(ii = g_list_sort(ids, intrevcmp); NULL != ii; ii = ii->next) {
index = GPOINTER_TO_INT(ii->data);
if(index >= count) {
assert(!"illegal torrent id");
continue;
}
tor = ii->data;
sb = tr_torrentStat(tor);
/* check if this action is valid for this torrent */
if(actionitems[actindex].nomenu ||
(actionitems[actindex].avail &&
!(actionitems[actindex].avail & sb[index].status)))
!(actionitems[actindex].avail & sb->status)))
continue;
switch(act) {
case ACT_START:
tr_torrentStart(data->tr, index);
tr_torrentStart(tor);
updatesave = TRUE;
break;
case ACT_STOP:
tr_torrentStop(data->tr, index);
tr_torrentStop(tor);
updatesave = TRUE;
break;
case ACT_DELETE:
if(TR_TORRENT_NEEDS_STOP(sb[index].status))
tr_torrentStop(data->tr, index);
tr_torrentClose(data->tr, index);
if(TR_STATUS_ACTIVE & sb->status)
tr_torrentStop(tor);
tr_torrentClose(data->tr, tor);
findtorrent(data->model, tor, &iter);
gtk_list_store_remove(GTK_LIST_STORE(data->model), &iter);
updatesave = TRUE;
/* XXX should only unselect deleted rows */
gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(data->view));
gtk_tree_selection_unselect_all(
gtk_tree_view_get_selection(data->view));
break;
case ACT_INFO:
makeinfowind(data->wind, data->tr, index);
makeinfowind(data->wind, tor);
break;
default:
assert(!"unknown type");
break;
}
}
free(sb);
if(updatesave) {
savetorrents(data->tr, data->wind, -1, NULL);
savetorrents(data->tr, data->wind);
updatemodel(data);
}
unblocksigs();
if(FROM_BUTTON == from)
g_list_free(ids);
}
void
findtorrent(GtkTreeModel *model, tr_torrent_t *tor, GtkTreeIter *iter) {
gpointer ptr;
if(gtk_tree_model_get_iter_first(model, iter)) {
do {
gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
if(tor == ptr)
return;
} while(gtk_tree_model_iter_next(model, iter));
}
assert(!"torrent not found");
}
gint
intrevcmp(gconstpointer a, gconstpointer b) {
int aint = GPOINTER_TO_INT(a);
@@ -1048,56 +1066,73 @@ doubleclick(GtkWidget *widget SHUTUP, GtkTreePath *path,
GtkTreeViewColumn *col SHUTUP, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreeIter iter;
int index;
tr_torrent_t *tor;
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);
makeinfowind(data->wind, data->tr, index);
if(gtk_tree_model_get_iter(data->model, &iter, path)) {
gtk_tree_model_get(data->model, &iter, MC_TORRENT, &tor, -1);
makeinfowind(data->wind, tor);
}
}
gboolean
addtorrent(tr_handle_t *tr, GtkWindow *parentwind, const char *torrent,
const char *dir, gboolean paused, GList **errs) {
addtorrent(void *vdata, const char *torrent, const char *dir, gboolean paused,
GList **errs) {
const struct { const int err; const char *msg; } errstrs[] = {
{TR_EINVALID, N_("not a valid torrent file")},
{TR_EDUPLICATE, N_("torrent is already open")},
};
struct cbdata *data = vdata;
tr_torrent_t *new;
char *wd;
int err;
unsigned int ii;
GtkTreeIter iter;
if(NULL == dir && NULL != (dir = cf_getpref(PREF_DIR))) {
if(!mkdir_p(dir, 0777)) {
errmsg(parentwind, _("Failed to create the directory %s:\n%s"),
errmsg(data->wind, _("Failed to create the directory %s:\n%s"),
dir, strerror(errno));
return FALSE;
}
}
blocksigs();
if(0 != tr_torrentInit(tr, torrent)) {
unblocksigs();
/* XXX would be nice to have errno strings, are they printed to stdout? */
if(NULL == errs)
errmsg(parentwind, _("Failed to load the torrent file %s"), torrent);
else
*errs = g_list_append(*errs, g_strdup(torrent));
if(NULL == (new = tr_torrentInit(data->tr, torrent, &err))) {
for(ii = 0; ii < ALEN(errstrs); ii++)
if(err == errstrs[ii].err)
break;
if(NULL == errs) {
if(ii == ALEN(errstrs))
errmsg(data->wind, _("Failed to load the torrent file %s"), torrent);
else
errmsg(data->wind, _("Failed to load the torrent file %s: %s"),
torrent, gettext(errstrs[ii].msg));
} else {
if(ii == ALEN(errstrs))
*errs = g_list_append(*errs, g_strdup(torrent));
else
*errs = g_list_append(*errs, g_strdup_printf(_("%s (%s)"),
torrent, gettext(errstrs[ii].msg)));
}
return FALSE;
}
gtk_list_store_append(GTK_LIST_STORE(data->model), &iter);
gtk_list_store_set(GTK_LIST_STORE(data->model), &iter, MC_TORRENT, new, -1);
if(NULL != dir)
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, dir);
tr_torrentSetFolder(new, dir);
else {
wd = g_new(char, MAXPATHLEN + 1);
if(NULL == getcwd(wd, MAXPATHLEN + 1))
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, ".");
tr_torrentSetFolder(new, ".");
else {
tr_torrentSetFolder(tr, tr_torrentCount(tr) - 1, wd);
tr_torrentSetFolder(new, wd);
free(wd);
}
}
if(!paused)
tr_torrentStart(tr, tr_torrentCount(tr) - 1);
unblocksigs();
tr_torrentStart(new);
return TRUE;
}
@@ -1107,32 +1142,25 @@ addedtorrents(void *vdata) {
struct cbdata *data = vdata;
updatemodel(data);
savetorrents(data->tr, data->wind, -1, NULL);
savetorrents(data->tr, data->wind);
}
gboolean
savetorrents(tr_handle_t *tr, GtkWindow *wind, int count, tr_stat_t *stat) {
savetorrents(tr_handle_t *tr, GtkWindow *wind) {
GList *torrents;
char *errstr;
tr_stat_t *st;
gboolean ret;
assert(NULL != tr || 0 <= count);
torrents = NULL;
tr_torrentIterate(tr, maketorrentlist, &torrents);
if(0 <= count)
ret = cf_savestate(count, stat, &errstr);
else {
blocksigs();
count = tr_torrentStat(tr, &st);
unblocksigs();
ret = cf_savestate(count, st, &errstr);
free(st);
}
if(!ret) {
if(!(ret = cf_savestate(torrents, &errstr))) {
errmsg(wind, "%s", errstr);
g_free(errstr);
}
g_list_free(torrents);
return ret;
}
@@ -1151,8 +1179,41 @@ void
makeidlist(GtkTreeModel *model, GtkTreePath *path SHUTUP, GtkTreeIter *iter,
gpointer gdata) {
GList **ids = gdata;
int index;
gpointer ptr;
gtk_tree_model_get(model, iter, MC_ROW_INDEX, &index, -1);
*ids = g_list_append(*ids, GINT_TO_POINTER(index));
gtk_tree_model_get(model, iter, MC_TORRENT, &ptr, -1);
*ids = g_list_append(*ids, ptr);
}
void
maketorrentlist(tr_torrent_t *tor, void *data) {
GList **list = data;
*list = g_list_append(*list, tor);
}
void
setupsighandlers(void) {
int sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
struct sigaction sa;
unsigned int ii;
bzero(&sa, sizeof(sa));
sa.sa_handler = fatalsig;
for(ii = 0; ii < ALEN(sigs); ii++)
sigaction(sigs[ii], &sa, NULL);
}
void
fatalsig(int sig) {
struct sigaction sa;
global_lastsig = sig;
if(SIGCOUNT_MAX <= ++global_sigcount) {
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(sig, &sa, NULL);
raise(sig);
}
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-02-10 00:50-0500\n"
"POT-Creation-Date: 2006-03-20 11:30-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -43,7 +43,7 @@ msgid ""
"%s"
msgstr ""
#: conf.c:115 dialogs.c:191 main.c:1065
#: conf.c:115 dialogs.c:190 main.c:1093
#, c-format
msgid ""
"Failed to create the directory %s:\n"
@@ -69,52 +69,52 @@ msgid ""
"%s"
msgstr ""
#: conf.c:233 conf.c:432
#: conf.c:233 conf.c:434
#, c-format
msgid ""
"Failed to open or lock the file %s:\n"
"%s"
msgstr ""
#: conf.c:253 conf.c:472
#: conf.c:253 conf.c:476
#, c-format
msgid ""
"Error while writing to the file %s:\n"
"%s"
msgstr ""
#: conf.c:260 conf.c:479
#: conf.c:260 conf.c:483
#, c-format
msgid ""
"Failed to rename the file %s to %s:\n"
"%s"
msgstr ""
#: dialogs.c:76
#: dialogs.c:75
#, c-format
msgid "%s Preferences"
msgstr ""
#: dialogs.c:84
#: dialogs.c:83
msgid "_Limit upload speed"
msgstr ""
#: dialogs.c:87
#: dialogs.c:86
msgid "Choose download directory"
msgstr ""
#. limit label and entry
#: dialogs.c:121
#: dialogs.c:120
msgid "Maximum _upload speed:"
msgstr ""
#. directory label and chooser
#: dialogs.c:135
#: dialogs.c:134
msgid "_Download directory:"
msgstr ""
#. port label and entry
#: dialogs.c:144
#: dialogs.c:143
msgid "Listening _port:"
msgstr ""
@@ -134,11 +134,11 @@ msgstr ""
msgid "Choose a download directory"
msgstr ""
#: dialogs.c:285
#: dialogs.c:284
msgid "Torrent files"
msgstr ""
#: dialogs.c:287
#: dialogs.c:286
msgid "All files"
msgstr ""
@@ -191,59 +191,59 @@ msgstr ""
msgid "Uploaded:"
msgstr ""
#: main.c:155
#: main.c:164
msgid "Add"
msgstr ""
#: main.c:156
#: main.c:165
msgid "Add a new torrent"
msgstr ""
#: main.c:157
#: main.c:166
msgid "Start"
msgstr ""
#: main.c:159
#: main.c:168
msgid "Start a torrent that is not running"
msgstr ""
#: main.c:160
#: main.c:169
msgid "Stop"
msgstr ""
#: main.c:162
#: main.c:171
msgid "Stop a torrent that is running"
msgstr ""
#: main.c:163
#: main.c:172
msgid "Remove"
msgstr ""
#: main.c:164
#: main.c:173
msgid "Remove a torrent"
msgstr ""
#: main.c:165
#: main.c:174
msgid "Properties"
msgstr ""
#: main.c:166
#: main.c:175
msgid "Show additional information about a torrent"
msgstr ""
#: main.c:167
#: main.c:176
msgid "Preferences"
msgstr ""
#: main.c:168
#: main.c:177
msgid "Customize application behavior"
msgstr ""
#: main.c:186
#: main.c:203
msgid "Transmission"
msgstr ""
#: main.c:316 main.c:518 main.c:1077
#: main.c:331 main.c:626 main.c:1105
#, c-format
msgid "Failed to load the torrent file %s"
msgid_plural ""
@@ -252,75 +252,98 @@ msgid_plural ""
msgstr[0] ""
msgstr[1] ""
#: main.c:606
#: main.c:422
msgid "Name"
msgstr ""
#. this string is only used to determing the size of the progress bar
#: main.c:613
#: main.c:429
msgid " fnord fnord "
msgstr ""
#: main.c:616
#: main.c:432
msgid "Progress"
msgstr ""
#: main.c:691
#: main.c:703
#, c-format
msgid "Checking existing files (%.1f%%)"
msgstr ""
#: main.c:693
#: main.c:706
#, c-format
msgid "Finishing in --:--:-- (%.1f%%)"
msgstr ""
#: main.c:708
#, c-format
msgid "Finishing in %02i:%02i:%02i (%.1f%%)"
msgstr ""
#: main.c:696
#: main.c:712
#, c-format
msgid "Seeding, uploading to %d of %d peer"
msgid_plural "Seeding, uploading to %d of %d peers"
msgstr[0] ""
msgstr[1] ""
#: main.c:700
#: main.c:716
msgid "Stopping..."
msgstr ""
#: main.c:702
#: main.c:718
#, c-format
msgid "Stopped (%.1f%%)"
msgstr ""
#: main.c:710
#: main.c:726
msgid "Error: "
msgstr ""
#: main.c:714
#: main.c:730
#, c-format
msgid "Downloading from %i of %i peer"
msgid_plural "Downloading from %i of %i peers"
msgstr[0] ""
msgstr[1] ""
#: main.c:748
#: main.c:763
#, c-format
msgid ""
"Ratio: %s\n"
"UL: %s/s"
msgstr ""
#: main.c:751
#: main.c:766
#, c-format
msgid ""
"DL: %s/s\n"
"UL: %s/s"
msgstr ""
#: main.c:799
#: main.c:812
#, c-format
msgid " Total DL: %s/s Total UL: %s/s"
msgstr ""
#: main.c:1081
msgid "not a valid torrent file"
msgstr ""
#: main.c:1082
msgid "torrent is already open"
msgstr ""
#: main.c:1107
#, c-format
msgid "Failed to load the torrent file %s: %s"
msgstr ""
#: main.c:1113
#, c-format
msgid "%s (%s)"
msgstr ""
#: util.c:63
msgid "B"
msgstr ""
@@ -349,11 +372,11 @@ msgstr ""
msgid "EiB"
msgstr ""
#: util.c:87
#: util.c:88
msgid "N/A"
msgstr ""
#. this is a UTF-8 infinity symbol
#: util.c:91
#: util.c:92
msgid "∞"
msgstr ""

View File

@@ -38,8 +38,6 @@
#define BESTDECIMAL(d) (10.0 > (d) ? 2 : (100.0 > (d) ? 1 : 0))
static void
sigexithandler(int sig);
static void
errcb(GtkWidget *wind, int resp, gpointer data);
@@ -185,71 +183,6 @@ urldecode(const char *str, int len) {
return ret;
}
static int exit_sigs[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGUSR2};
static callbackfunc_t exit_func = NULL;
static void *exit_data = NULL;
static int exit_block_level = 0;
void
setuphandlers(callbackfunc_t func, void *data) {
struct sigaction sa;
unsigned int ii;
exit_data = data;
exit_func = func;
bzero(&sa, sizeof(sa));
sa.sa_handler = sigexithandler;
for(ii = 0; ii < ALEN(exit_sigs); ii++)
sigaction(exit_sigs[ii], &sa, NULL);
}
void
clearhandlers(void) {
struct sigaction sa;
unsigned int ii;
bzero(&sa, sizeof(sa));
sa.sa_handler = SIG_DFL;
for(ii = 0; ii < ALEN(exit_sigs); ii++)
sigaction(exit_sigs[ii], &sa, NULL);
}
static void
sigexithandler(int sig) {
exit_func(exit_data);
clearhandlers();
raise(sig);
}
void
blocksigs(void) {
sigset_t mask;
unsigned int ii;
if(0 < (exit_block_level++))
return;
sigemptyset(&mask);
for(ii = 0; ii < ALEN(exit_sigs); ii++)
sigaddset(&mask, exit_sigs[ii]);
sigprocmask(SIG_BLOCK, &mask, NULL);
}
void
unblocksigs(void) {
sigset_t mask;
unsigned int ii;
if(0 < (--exit_block_level))
return;
sigemptyset(&mask);
for(ii = 0; ii < ALEN(exit_sigs); ii++)
sigaddset(&mask, exit_sigs[ii]);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
}
GtkWidget *
errmsg(GtkWindow *wind, const char *format, ...) {
GtkWidget *dialog;

View File

@@ -70,23 +70,6 @@ joinstrlist(GList *list, char *sep);
char *
urldecode(const char *str, int len);
/* set up a handler for various fatal signals */
void
setuphandlers(callbackfunc_t func, void *data);
/* clear the handlers for fatal signals */
void
clearhandlers(void);
/* blocks and unblocks delivery of fatal signals. calls to these
functions can be nested as long as unblocksigs() is called exactly
as many times as blocksigs(). only the first blocksigs() will
block signals and only the last unblocksigs() will unblock them. */
void
blocksigs(void);
void
unblocksigs(void);
/* if wind is NULL then you must call gtk_widget_show on the returned widget */
GtkWidget *

View File

@@ -138,7 +138,7 @@ static inline void sortPeers( tr_peer_t ** all, int allCount,
void tr_chokingPulse( tr_choking_t * c )
{
int i, peersTotalCount, unchoked, mustOptimistic = 1;
int peersTotalCount, unchoked, mustOptimistic = 1;
tr_peer_t ** canChoke, ** canUnchoke;
tr_peer_t ** canChokeZero, ** canUnchokeZero;
tr_peer_t ** canChokeNonZero, ** canUnchokeNonZero;
@@ -152,9 +152,8 @@ void tr_chokingPulse( tr_choking_t * c )
/* Lock all torrents and get the total number of peers */
peersTotalCount = 0;
for( i = 0; i < c->h->torrentCount; i++ )
for( tor = c->h->torrentList; tor; tor = tor->next )
{
tor = c->h->torrents[i];
tr_lockLock( &tor->lock );
peersTotalCount += tor->peerCount;
}
@@ -165,15 +164,14 @@ void tr_chokingPulse( tr_choking_t * c )
canUnchokeCount = 0;
unchoked = 0;
for( i = 0; i < c->h->torrentCount; i++ )
for( tor = c->h->torrentList; tor; tor = tor->next )
{
tr_peer_t * peer;
int j;
int i;
tor = c->h->torrents[i];
for( j = 0; j < tor->peerCount; j++ )
for( i = 0; i < tor->peerCount; i++ )
{
peer = tor->peers[j];
peer = tor->peers[i];
if( !tr_peerIsConnected( peer ) )
continue;
@@ -320,9 +318,9 @@ void tr_chokingPulse( tr_choking_t * c )
free( canUnchokeNonZero );
/* Unlock all torrents */
for( i = 0; i < c->h->torrentCount; i++ )
for( tor = c->h->torrentList; tor; tor = tor->next )
{
tr_lockUnlock( &c->h->torrents[i]->lock );
tr_lockUnlock( &tor->lock );
}
tr_lockUnlock( &c->lock );

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -82,7 +82,6 @@
#define TR_MAX_PEER_COUNT 60
typedef struct tr_torrent_s tr_torrent_t;
typedef struct tr_completion_s tr_completion_t;
#include "platform.h"
@@ -108,11 +107,13 @@ struct tr_torrent_s
tr_fd_t * fdlimit;
int status;
int finished;
char error[128];
int error;
char trackerError[128];
int finished;
char * id;
char * key;
int * bindPort;
/* An escaped string used to include the hash in HTTP queries */
char hashString[3*SHA_DIGEST_LENGTH+1];
@@ -126,14 +127,6 @@ struct tr_torrent_s
int blockSize;
int blockCount;
#if 0
/* Status for each block
-1 = we have it
n = we are downloading it from n peers */
char * blockHave;
int blockHaveCount;
uint8_t * bitfield;
#endif
tr_completion_t * completion;
volatile char die;
@@ -150,6 +143,12 @@ struct tr_torrent_s
uint64_t date;
uint64_t downloaded;
uint64_t uploaded;
tr_stat_t stats[2];
int statCur;
tr_torrent_t * prev;
tr_torrent_t * next;
};
#include "utils.h"
@@ -158,7 +157,7 @@ struct tr_torrent_s
struct tr_handle_s
{
int torrentCount;
tr_torrent_t * torrents[TR_MAX_TORRENT_COUNT];
tr_torrent_t * torrentList;
tr_ratecontrol_t * upload;
tr_ratecontrol_t * download;

View File

@@ -57,13 +57,13 @@ struct tr_tracker_s
static void sendQuery ( tr_tracker_t * tc );
static void recvAnswer ( tr_tracker_t * tc );
tr_tracker_t * tr_trackerInit( tr_handle_t * h, tr_torrent_t * tor )
tr_tracker_t * tr_trackerInit( tr_torrent_t * tor )
{
tr_tracker_t * tc;
tc = calloc( 1, sizeof( tr_tracker_t ) );
tc->tor = tor;
tc->id = h->id;
tc->id = tor->id;
tc->started = 1;
@@ -74,7 +74,7 @@ tr_tracker_t * tr_trackerInit( tr_handle_t * h, tr_torrent_t * tor )
tc->size = 1024;
tc->buf = malloc( tc->size );
tc->bindPort = h->bindPort;
tc->bindPort = *(tor->bindPort);
tc->newPort = -1;
return tc;
@@ -374,13 +374,12 @@ static void recvAnswer( tr_tracker_t * tc )
if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) )
{
tr_err( "Tracker: %s", bePeers->val.s.s );
tor->status |= TR_TRACKER_ERROR;
snprintf( tor->error, sizeof( tor->error ),
tor->error |= TR_ETRACKER;
snprintf( tor->trackerError, sizeof( tor->trackerError ),
"%s", bePeers->val.s.s );
goto cleanup;
}
tor->status &= ~TR_TRACKER_ERROR;
tor->error &= ~TR_ETRACKER;
if( !tc->interval )
{

View File

@@ -25,7 +25,7 @@
typedef struct tr_tracker_s tr_tracker_t;
tr_tracker_t * tr_trackerInit ( tr_handle_t *, tr_torrent_t * );
tr_tracker_t * tr_trackerInit ( tr_torrent_t * );
void tr_trackerChangePort( tr_tracker_t *, int );
int tr_trackerPulse ( tr_tracker_t * );
void tr_trackerCompleted ( tr_tracker_t * );

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -25,7 +25,7 @@
/***********************************************************************
* Local prototypes
**********************************************************************/
static void torrentReallyStop( tr_handle_t * h, int t );
static void torrentReallyStop( tr_torrent_t * );
static void downloadLoop( void * );
static void acceptLoop( void * );
static void acceptStop( tr_handle_t * h );
@@ -85,7 +85,8 @@ tr_handle_t * tr_init()
**********************************************************************/
void tr_setBindPort( tr_handle_t * h, int port )
{
int ii, sock;
int sock;
tr_torrent_t * tor;
if( h->bindPort == port )
return;
@@ -106,14 +107,14 @@ void tr_setBindPort( tr_handle_t * h, int port )
h->bindPort = port;
for( ii = 0; ii < h->torrentCount; ii++ )
for( tor = h->torrentList; tor; tor = tor->next )
{
tr_lockLock( &h->torrents[ii]->lock );
if( NULL != h->torrents[ii]->tracker )
tr_lockLock( &tor->lock );
if( NULL != tor->tracker )
{
tr_trackerChangePort( h->torrents[ii]->tracker, port );
tr_trackerChangePort( tor->tracker, port );
}
tr_lockUnlock( &h->torrents[ii]->lock );
tr_lockUnlock( &tor->lock );
}
if( h->bindSocket > -1 )
@@ -146,13 +147,11 @@ void tr_setUploadLimit( tr_handle_t * h, int limit )
void tr_torrentRates( tr_handle_t * h, float * dl, float * ul )
{
tr_torrent_t * tor;
int i;
*dl = 0.0;
*ul = 0.0;
for( i = 0; i < h->torrentCount; i++ )
for( tor = h->torrentList; tor; tor = tor->next )
{
tor = h->torrents[i];
tr_lockLock( &tor->lock );
if( tor->status & TR_STATUS_DOWNLOAD )
*dl += tr_rcRate( tor->download );
@@ -167,44 +166,41 @@ void tr_torrentRates( tr_handle_t * h, float * dl, float * ul )
* Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
* to fill it.
**********************************************************************/
int tr_torrentInit( tr_handle_t * h, const char * path )
tr_torrent_t * tr_torrentInit( tr_handle_t * h, const char * path,
int * error )
{
tr_torrent_t * tor;
tr_torrent_t * tor, * tor_tmp;
tr_info_t * inf;
int i;
char * s1, * s2;
if( h->torrentCount >= TR_MAX_TORRENT_COUNT )
{
tr_err( "Maximum number of torrents reached" );
return 1;
}
tor = calloc( sizeof( tr_torrent_t ), 1 );
inf = &tor->info;
/* Parse torrent file */
if( tr_metainfoParse( inf, path ) )
{
*error = TR_EINVALID;
free( tor );
return 1;
return NULL;
}
/* Make sure this torrent is not already open */
for( i = 0; i < h->torrentCount; i++ )
for( tor_tmp = h->torrentList; tor_tmp; tor_tmp = tor_tmp->next )
{
if( !memcmp( tor->info.hash, h->torrents[i]->info.hash,
if( !memcmp( tor->info.hash, tor_tmp->info.hash,
SHA_DIGEST_LENGTH ) )
{
tr_err( "Torrent already open" );
*error = TR_EDUPLICATE;
free( tor );
return 1;
return NULL;
}
}
tor->status = TR_STATUS_PAUSE;
tor->id = h->id;
tor->key = h->key;
tor->bindPort = &h->bindPort;
tor->finished = 0;
@@ -246,65 +242,60 @@ int tr_torrentInit( tr_handle_t * h, const char * path )
/* We have a new torrent */
tr_lockLock( &h->acceptLock );
h->torrents[h->torrentCount] = tor;
tor->prev = NULL;
tor->next = h->torrentList;
if( tor->next )
{
tor->next->prev = tor;
}
h->torrentList = tor;
(h->torrentCount)++;
tr_lockUnlock( &h->acceptLock );
return 0;
return tor;
}
tr_info_t * tr_torrentInfo( tr_torrent_t * tor )
{
return &tor->info;
}
/***********************************************************************
* tr_torrentScrape
***********************************************************************
* Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
* to fill it.
**********************************************************************/
int tr_torrentScrape( tr_handle_t * h, int t, int * s, int * l )
int tr_torrentScrape( tr_torrent_t * tor, int * s, int * l )
{
return tr_trackerScrape( h->torrents[t], s, l );
return tr_trackerScrape( tor, s, l );
}
void tr_torrentSetFolder( tr_handle_t * h, int t, const char * path )
void tr_torrentSetFolder( tr_torrent_t * tor, const char * path )
{
tr_torrent_t * tor = h->torrents[t];
tor->destination = strdup( path );
}
char * tr_torrentGetFolder( tr_handle_t * h, int t )
char * tr_torrentGetFolder( tr_torrent_t * tor )
{
tr_torrent_t * tor = h->torrents[t];
return tor->destination;
}
void tr_torrentStart( tr_handle_t * h, int t )
void tr_torrentStart( tr_torrent_t * tor )
{
tr_torrent_t * tor = h->torrents[t];
if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
{
/* Join the thread first */
torrentReallyStop( h, t );
torrentReallyStop( tor );
}
tor->status = TR_STATUS_CHECK;
tor->tracker = tr_trackerInit( h, tor );
if( 0 > h->bindPort )
{
tr_setBindPort( h, TR_DEFAULT_PORT );
}
tor->status = TR_STATUS_CHECK;
tor->tracker = tr_trackerInit( tor );
tor->date = tr_date();
tor->die = 0;
tr_threadCreate( &tor->thread, downloadLoop, tor );
}
void tr_torrentStop( tr_handle_t * h, int t )
void tr_torrentStop( tr_torrent_t * tor )
{
tr_torrent_t * tor = h->torrents[t];
tr_lockLock( &tor->lock );
tr_trackerStopped( tor->tracker );
tr_rcReset( tor->download );
@@ -319,10 +310,8 @@ void tr_torrentStop( tr_handle_t * h, int t )
***********************************************************************
* Joins the download thread and frees/closes everything related to it.
**********************************************************************/
static void torrentReallyStop( tr_handle_t * h, int t )
static void torrentReallyStop( tr_torrent_t * tor )
{
tr_torrent_t * tor = h->torrents[t];
tor->die = 1;
tr_threadJoin( &tor->thread );
tr_dbg( "Thread joined" );
@@ -345,129 +334,131 @@ int tr_torrentCount( tr_handle_t * h )
return h->torrentCount;
}
int tr_getFinished( tr_handle_t * h, int i)
void tr_torrentIterate( tr_handle_t * h, tr_callback_t func, void * d )
{
return h->torrents[i]->finished;
}
void tr_setFinished( tr_handle_t * h, int i, int val)
{
h->torrents[i]->finished = val;
tr_torrent_t * tor;
for( tor = h->torrentList; tor; tor = tor->next )
{
func( tor, d );
}
}
int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
int tr_getFinished( tr_torrent_t * tor )
{
if( tor->finished )
{
tor->finished = 0;
return 1;
}
return 0;
}
tr_stat_t * tr_torrentStat( tr_torrent_t * tor )
{
tr_stat_t * s;
tr_torrent_t * tor;
tr_info_t * inf;
int i, j, k, piece;
tr_info_t * inf = &tor->info;
int i;
if( h->torrentCount < 1 )
tor->statCur = ( tor->statCur + 1 ) % 2;
s = &tor->stats[tor->statCur];
if( ( tor->status & TR_STATUS_STOPPED ) ||
( ( tor->status & TR_STATUS_STOPPING ) &&
tr_date() > tor->stopDate + 60000 ) )
{
*stat = NULL;
return 0;
torrentReallyStop( tor );
tor->status = TR_STATUS_PAUSE;
}
s = malloc( h->torrentCount * sizeof( tr_stat_t ) );
tr_lockLock( &tor->lock );
for( i = 0; i < h->torrentCount; i++ )
s->status = tor->status;
memcpy( s->trackerError, tor->trackerError,
sizeof( s->trackerError ) );
s->peersTotal = 0;
s->peersUploading = 0;
s->peersDownloading = 0;
for( i = 0; i < tor->peerCount; i++ )
{
tor = h->torrents[i];
inf = &tor->info;
if( ( tor->status & TR_STATUS_STOPPED ) ||
( ( tor->status & TR_STATUS_STOPPING ) &&
tr_date() > tor->stopDate + 60000 ) )
if( tr_peerIsConnected( tor->peers[i] ) )
{
torrentReallyStop( h, i );
tor->status = TR_STATUS_PAUSE;
(s->peersTotal)++;
if( tr_peerIsUploading( tor->peers[i] ) )
{
(s->peersUploading)++;
}
if( tr_peerIsDownloading( tor->peers[i] ) )
{
(s->peersDownloading)++;
}
}
}
s->progress = tr_cpCompletionAsFloat( tor->completion );
if( tor->status & TR_STATUS_DOWNLOAD )
s->rateDownload = tr_rcRate( tor->download );
else
/* tr_rcRate() doesn't make the difference between 'piece'
messages and other messages, which causes a non-zero
download rate even tough we are not downloading. So we
force it to zero not to confuse the user. */
s->rateDownload = 0.0;
s->rateUpload = tr_rcRate( tor->upload );
s->seeders = tr_trackerSeeders(tor);
s->leechers = tr_trackerLeechers(tor);
if( s->rateDownload < 0.1 )
{
s->eta = -1;
}
else
{
s->eta = (float) ( 1.0 - s->progress ) *
(float) inf->totalSize / s->rateDownload / 1024.0;
if( s->eta > 99 * 3600 + 59 * 60 + 59 )
{
s->eta = -1;
}
}
s->downloaded = tor->downloaded;
s->uploaded = tor->uploaded;
tr_lockUnlock( &tor->lock );
return s;
}
void tr_torrentAvailability( tr_torrent_t * tor, int8_t * tab, int size )
{
int i, j, piece;
tr_lockLock( &tor->lock );
for( i = 0; i < size; i++ )
{
piece = i * tor->info.pieceCount / size;
if( tr_cpPieceIsComplete( tor->completion, piece ) )
{
tab[i] = -1;
continue;
}
tr_lockLock( &tor->lock );
memcpy( &s[i].info, &tor->info, sizeof( tr_info_t ) );
s[i].status = tor->status;
memcpy( s[i].error, tor->error, sizeof( s[i].error ) );
s[i].peersTotal = 0;
s[i].peersUploading = 0;
s[i].peersDownloading = 0;
tab[i] = 0;
for( j = 0; j < tor->peerCount; j++ )
{
if( tr_peerIsConnected( tor->peers[j] ) )
if( tr_peerBitfield( tor->peers[j] ) &&
tr_bitfieldHas( tr_peerBitfield( tor->peers[j] ), piece ) )
{
(s[i].peersTotal)++;
if( tr_peerIsUploading( tor->peers[j] ) )
{
(s[i].peersUploading)++;
}
if( tr_peerIsDownloading( tor->peers[j] ) )
{
(s[i].peersDownloading)++;
}
(tab[i])++;
}
}
s[i].progress = tr_cpCompletionAsFloat( tor->completion );
if( tor->status & TR_STATUS_DOWNLOAD )
s[i].rateDownload = tr_rcRate( tor->download );
else
/* tr_rcRate() doesn't make the difference between 'piece'
messages and other messages, which causes a non-zero
download rate even tough we are not downloading. So we
force it to zero not to confuse the user. */
s[i].rateDownload = 0.0;
s[i].rateUpload = tr_rcRate( tor->upload );
s[i].seeders = tr_trackerSeeders(tor);
s[i].leechers = tr_trackerLeechers(tor);
if( s[i].rateDownload < 0.1 )
{
s[i].eta = -1;
}
else
{
s[i].eta = (float) ( 1.0 - s[i].progress ) *
(float) inf->totalSize / s[i].rateDownload / 1024.0;
if( s[i].eta > 99 * 3600 + 59 * 60 + 59 )
{
s[i].eta = -1;
}
}
for( j = 0; j < 120; j++ )
{
piece = j * inf->pieceCount / 120;
if( tr_cpPieceIsComplete( tor->completion, piece ) )
{
s[i].pieces[j] = -1;
continue;
}
s[i].pieces[j] = 0;
for( k = 0; k < tor->peerCount; k++ )
{
if( tr_peerBitfield( tor->peers[k] ) &&
tr_bitfieldHas( tr_peerBitfield( tor->peers[k] ), piece ) )
{
(s[i].pieces[j])++;
}
}
}
s[i].downloaded = tor->downloaded;
s[i].uploaded = tor->uploaded;
s[i].folder = tor->destination;
tr_lockUnlock( &tor->lock );
}
*stat = s;
return h->torrentCount;
tr_lockUnlock( &tor->lock );
}
/***********************************************************************
@@ -475,15 +466,14 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
***********************************************************************
* Frees memory allocated by tr_torrentInit.
**********************************************************************/
void tr_torrentClose( tr_handle_t * h, int t )
void tr_torrentClose( tr_handle_t * h, tr_torrent_t * tor )
{
tr_torrent_t * tor = h->torrents[t];
tr_info_t * inf = &tor->info;
tr_info_t * inf = &tor->info;
if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
{
/* Join the thread first */
torrentReallyStop( h, t );
torrentReallyStop( tor );
}
tr_lockLock( &h->acceptLock );
@@ -502,10 +492,20 @@ void tr_torrentClose( tr_handle_t * h, int t )
}
free( inf->pieces );
free( inf->files );
free( tor );
memmove( &h->torrents[t], &h->torrents[t+1],
( h->torrentCount - t ) * sizeof( void * ) );
if( tor->prev )
{
tor->prev->next = tor->next;
}
else
{
h->torrentList = tor->next;
}
if( tor->next )
{
tor->next->prev = tor->prev;
}
free( tor );
tr_lockUnlock( &h->acceptLock );
}
@@ -595,8 +595,9 @@ static void acceptLoop( void * _h )
{
tr_handle_t * h = _h;
uint64_t date1, date2, lastchoke = 0;
int ii, jj;
int ii;
uint8_t * hash;
tr_torrent_t * tor;
tr_dbg( "Accept thread started" );
@@ -640,17 +641,17 @@ static void acceptLoop( void * _h )
}
if( NULL != ( hash = tr_peerHash( h->acceptPeers[ii] ) ) )
{
for( jj = 0; jj < h->torrentCount; jj++ )
for( tor = h->torrentList; tor; tor = tor->next )
{
tr_lockLock( &h->torrents[jj]->lock );
if( 0 == memcmp( h->torrents[jj]->info.hash, hash,
tr_lockLock( &tor->lock );
if( 0 == memcmp( tor->info.hash, hash,
SHA_DIGEST_LENGTH ) )
{
tr_peerAttach( h->torrents[jj], h->acceptPeers[ii] );
tr_lockUnlock( &h->torrents[jj]->lock );
tr_peerAttach( tor, h->acceptPeers[ii] );
tr_lockUnlock( &tor->lock );
goto removePeer;
}
tr_lockUnlock( &h->torrents[jj]->lock );
tr_lockUnlock( &tor->lock );
}
tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
goto removePeer;

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -29,16 +29,16 @@ extern "C" {
#include <inttypes.h>
#define SHA_DIGEST_LENGTH 20
#define SHA_DIGEST_LENGTH 20
#ifdef __BEOS__
# include <StorageDefs.h>
# define MAX_PATH_LENGTH B_FILE_NAME_LENGTH
# define MAX_PATH_LENGTH B_FILE_NAME_LENGTH
#else
# define MAX_PATH_LENGTH 1024
# define MAX_PATH_LENGTH 1024
#endif
#define TR_MAX_TORRENT_COUNT 50
#define TR_DEFAULT_PORT 9090
#define TR_DEFAULT_PORT 9090
#define TR_NOERROR 0
/***********************************************************************
* tr_init
@@ -47,7 +47,6 @@ extern "C" {
* be passed to all functions below.
**********************************************************************/
typedef struct tr_handle_s tr_handle_t;
tr_handle_t * tr_init();
/***********************************************************************
@@ -74,6 +73,22 @@ void tr_setBindPort( tr_handle_t *, int );
**********************************************************************/
void tr_setUploadLimit( tr_handle_t *, int );
/***********************************************************************
* tr_torrentCount
***********************************************************************
* Returns the count of open torrents
**********************************************************************/
int tr_torrentCount( tr_handle_t * h );
/***********************************************************************
* tr_torrentIterate
***********************************************************************
* Iterates on open torrents
**********************************************************************/
typedef struct tr_torrent_s tr_torrent_t;
typedef void (*tr_callback_t) ( tr_torrent_t *, void * );
void tr_torrentIterate( tr_handle_t *, tr_callback_t, void * );
/***********************************************************************
* tr_torrentRates
***********************************************************************
@@ -82,27 +97,29 @@ void tr_setUploadLimit( tr_handle_t *, int );
void tr_torrentRates( tr_handle_t *, float *, float * );
/***********************************************************************
* tr_getFinished
* tr_close
***********************************************************************
* Tests to see if torrent is finished
* Frees memory allocated by tr_init.
**********************************************************************/
int tr_getFinished( tr_handle_t *, int );
/***********************************************************************
* tr_setFinished
***********************************************************************
* Sets the boolean value finished in the torrent back to false
**********************************************************************/
void tr_setFinished( tr_handle_t *, int, int );
void tr_close( tr_handle_t * );
/***********************************************************************
* tr_torrentInit
***********************************************************************
* Opens and parses torrent file at 'path'. If the file exists and is a
* valid torrent file, returns 0 and adds it to the list of torrents
* (but doesn't start it). Returns a non-zero value otherwise.
* valid torrent file, returns an handle and adds it to the list of
* torrents (but doesn't start it). Returns NULL and sets *error
* otherwise.
**********************************************************************/
int tr_torrentInit( tr_handle_t *, const char * path );
#define TR_EINVALID 1
#define TR_EUNSUPPORTED 2
#define TR_EDUPLICATE 3
#define TR_EOTHER 666
tr_torrent_t * tr_torrentInit( tr_handle_t *, const char * path,
int * error );
typedef struct tr_info_s tr_info_t;
tr_info_t * tr_torrentInfo( tr_torrent_t * );
/***********************************************************************
* tr_torrentScrape
@@ -113,7 +130,7 @@ int tr_torrentInit( tr_handle_t *, const char * path );
* replied with some error. tr_torrentScrape may block up to 20 seconds
* before returning.
**********************************************************************/
int tr_torrentScrape( tr_handle_t *, int, int * s, int * l );
int tr_torrentScrape( tr_torrent_t *, int * s, int * l );
/***********************************************************************
* tr_torrentStart
@@ -121,9 +138,9 @@ int tr_torrentScrape( tr_handle_t *, int, int * s, int * l );
* Starts downloading. The download is launched in a seperate thread,
* therefore tr_torrentStart returns immediately.
**********************************************************************/
void tr_torrentSetFolder( tr_handle_t *, int, const char * );
char * tr_torrentGetFolder( tr_handle_t *, int );
void tr_torrentStart( tr_handle_t *, int );
void tr_torrentSetFolder( tr_torrent_t *, const char * );
char * tr_torrentGetFolder( tr_torrent_t * );
void tr_torrentStart( tr_torrent_t * );
/***********************************************************************
* tr_torrentStop
@@ -136,24 +153,38 @@ void tr_torrentStart( tr_handle_t *, int );
* - by tr_torrentClose if you choose to remove the torrent without
* waiting any further.
**********************************************************************/
void tr_torrentStop( tr_handle_t *, int );
void tr_torrentStop( tr_torrent_t * );
/***********************************************************************
* tr_getFinished
***********************************************************************
* The first call after a torrent is completed returns 1. Returns 0
* in other cases.
**********************************************************************/
int tr_getFinished( tr_torrent_t * );
/***********************************************************************
* tr_torrentStat
***********************************************************************
* Allocates an array of tr_stat_t structures, containing information
* about the current status of all open torrents (see the contents
* of tr_stat_s below). Returns the count of open torrents and sets
* (*s) to the address of the array, or NULL if no torrent is open.
* In the former case, the array belongs to the caller who is
* responsible of freeing it.
* The interface should call this function every 0.5 second or so in
* order to update itself.
* Returns a pointer to an tr_stat_t structure with updated information
* on the torrent. The structure belongs to libtransmission (do not
* free it) and is guaranteed to be unchanged until the next call to
* tr_torrentStat.
* The interface should call this function every second or so in order
* to update itself.
**********************************************************************/
typedef struct tr_stat_s tr_stat_t;
tr_stat_t * tr_torrentStat( tr_torrent_t * );
int tr_torrentCount( tr_handle_t * h );
int tr_torrentStat( tr_handle_t *, tr_stat_t ** s );
/***********************************************************************
* tr_torrentAvailability
***********************************************************************
* Use this to draw an advanced progress bar which is 'size' pixels
* wide. Fills 'tab' which you must have allocated: each byte is set
* to either -1 if we have the piece, otherwise it is set to the number
* of connected peers who have the piece.
**********************************************************************/
void tr_torrentAvailability( tr_torrent_t *, int8_t * tab, int size );
/***********************************************************************
* tr_torrentClose
@@ -161,18 +192,11 @@ int tr_torrentStat( tr_handle_t *, tr_stat_t ** s );
* Frees memory allocated by tr_torrentInit. If the torrent was running,
* you must call tr_torrentStop() before closing it.
**********************************************************************/
void tr_torrentClose( tr_handle_t *, int );
/***********************************************************************
* tr_close
***********************************************************************
* Frees memory allocated by tr_init.
**********************************************************************/
void tr_close( tr_handle_t * );
void tr_torrentClose( tr_handle_t *, tr_torrent_t * );
/***********************************************************************
* tr_stat_s
* tr_info_s
**********************************************************************/
typedef struct tr_file_s
{
@@ -180,8 +204,7 @@ typedef struct tr_file_s
char name[MAX_PATH_LENGTH]; /* Path to the file */
}
tr_file_t;
typedef struct tr_info_s
struct tr_info_s
{
/* Path to torrent */
char torrent[MAX_PATH_LENGTH];
@@ -204,13 +227,13 @@ typedef struct tr_info_s
/* Files info */
int fileCount;
tr_file_t * files;
}
tr_info_t;
};
/***********************************************************************
* tr_stat_s
**********************************************************************/
struct tr_stat_s
{
tr_info_t info;
#define TR_STATUS_CHECK 0x001 /* Checking files */
#define TR_STATUS_DOWNLOAD 0x002 /* Downloading */
#define TR_STATUS_SEED 0x004 /* Seeding */
@@ -218,9 +241,15 @@ struct tr_stat_s
#define TR_STATUS_STOPPED 0x010 /* Sent 'stopped' but thread still
running (for internal use only) */
#define TR_STATUS_PAUSE 0x020 /* Paused */
#define TR_TRACKER_ERROR 0x100
#define TR_STATUS_ACTIVE (TR_STATUS_CHECK|TR_STATUS_DOWNLOAD|TR_STATUS_SEED)
#define TR_STATUS_INACTIVE (TR_STATUS_STOPPING|TR_STATUS_STOPPED|TR_STATUS_PAUSE)
int status;
char error[128];
#define TR_ETRACKER 1
#define TR_EINOUT 2
int error;
char trackerError[128];
float progress;
float rateDownload;
@@ -229,14 +258,11 @@ struct tr_stat_s
int peersTotal;
int peersUploading;
int peersDownloading;
char pieces[120];
int seeders;
int leechers;
uint64_t downloaded;
uint64_t uploaded;
char * folder;
};
#ifdef __TRANSMISSION__

View File

@@ -1,11 +1,27 @@
//
// Badger.m
// Transmission
//
// Created by Mitchell Livingston on 12/21/05.
//
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "Badger.h"
#import "StringAdditions.h"
@interface Badger (Private)
@@ -87,7 +103,7 @@
badgeRect.origin.y += badgeBottomExtra;
//place badge text
[self badgeString: [NSString stringWithFormat: @"%d", completed]
[self badgeString: [NSString stringWithInt: completed]
forRect: badgeRect];
[dockIcon unlockFocus];

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -32,10 +32,9 @@
@interface Controller : NSObject
{
tr_handle_t * fHandle;
tr_handle_t * fLib;
int fCount, fSeeding, fDownloading, fCompleted;
tr_stat_t * fStat;
int fResumeOnWake[TR_MAX_TORRENT_COUNT];
NSMutableArray * fTorrents;
NSToolbar * fToolbar;
@@ -127,8 +126,6 @@
- (void) linkForums: (id) sender;
- (void) notifyGrowl: (NSString *) file;
- (void) revealFromMenu: (id) sender;
- (void) finderReveal: (NSString *) path;
- (void) finderTrash: (NSString *) path;
- (void) growlRegister: (id) sender;
- (void) checkForUpdate: (id) sender;

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -22,8 +22,9 @@
#import <IOKit/IOMessage.h>
#import "NameCell.h"
#import "ProgressCell.h"
#import "Controller.h"
#import "Torrent.h"
#import "TorrentCell.h"
#import "StringAdditions.h"
#import "Utils.h"
#import "TorrentTableView.h"
@@ -52,21 +53,33 @@ static void sleepCallBack( void * controller, io_service_t y,
@implementation Controller
- (id) init
{
fLib = tr_init();
fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
return self;
}
- (void) dealloc
{
[fTorrents release];
tr_close( fLib );
[super dealloc];
}
- (void) awakeFromNib
{
[fWindow setContentMinSize: NSMakeSize( 400, 120 )];
fHandle = tr_init();
[fPrefsController setPrefsWindow: fHandle];
[fPrefsController setPrefsWindow: fLib];
fDefaults = [NSUserDefaults standardUserDefaults];
[fInfoPanel setFrameAutosaveName:@"InfoPanel"];
//check advanced bar menu item
[fAdvancedBarItem setState: [fDefaults
boolForKey:@"UseAdvancedBar"] ? NSOnState : NSOffState];
fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
[fToolbar setDelegate: self];
[fToolbar setAllowsUserCustomization: YES];
@@ -74,18 +87,9 @@ static void sleepCallBack( void * controller, io_service_t y,
[fWindow setToolbar: fToolbar];
[fWindow setDelegate: self];
NSTableColumn * tableColumn;
NameCell * nameCell = [[NameCell alloc] init];
ProgressCell * progressCell = [[ProgressCell alloc] init];
tableColumn = [fTableView tableColumnWithIdentifier: @"Name"];
[tableColumn setDataCell: nameCell];
tableColumn = [fTableView tableColumnWithIdentifier: @"Progress"];
[tableColumn setDataCell: progressCell];
[fTableView setAutosaveTableColumns: YES];
//[fTableView sizeToFit];
[fTableView setTorrents: fTorrents];
[[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell:
[[TorrentCell alloc] init]];
[fTableView registerForDraggedTypes: [NSArray arrayWithObjects:
NSFilenamesPboardType, NULL]];
@@ -108,24 +112,27 @@ static void sleepCallBack( void * controller, io_service_t y,
NSString * torrentPath, * downloadFolder, * paused;
NSDictionary * dic;
Torrent * torrent;
NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
while ((dic = [enumerator nextObject]))
{
torrentPath = [dic objectForKey: @"TorrentPath"];
downloadFolder = [dic objectForKey: @"DownloadFolder"];
paused = [dic objectForKey: @"Paused"];
if (!torrentPath || !downloadFolder || !paused)
continue;
if (tr_torrentInit(fHandle, [torrentPath UTF8String]))
torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib];
if( !torrent )
continue;
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[downloadFolder UTF8String] );
[fTorrents addObject: torrent];
[torrent setFolder: downloadFolder];
if ([paused isEqualToString: @"NO"])
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
[torrent start];
}
//check and register Growl if it is installed for this user or all users
@@ -137,13 +144,11 @@ static void sleepCallBack( void * controller, io_service_t y,
//initialize badging
fBadger = [[Badger alloc] init];
//update the interface every 500 ms
fCount = 0;
fDownloading = 0;
fSeeding = 0;
fCompleted = 0;
fStat = nil;
[self updateUI: nil];
fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
selector: @selector( updateUI: ) userInfo: nil repeats: YES];
@@ -184,7 +189,17 @@ static void sleepCallBack( void * controller, io_service_t y,
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
int active = fDownloading + fSeeding;
int active = 0;
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
if( [torrent isActive] )
{
active++;
}
}
if (active > 0 && [fDefaults boolForKey: @"CheckQuit"])
{
NSString * message = active == 1
@@ -199,8 +214,8 @@ static void sleepCallBack( void * controller, io_service_t y,
@selector(quitSheetDidEnd:returnCode:contextInfo:),
nil, nil, message);
return NSTerminateLater;
}
}
return NSTerminateNow;
}
@@ -214,40 +229,41 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) applicationWillTerminate: (NSNotification *) notification
{
int i;
NSEnumerator * enumerator;
Torrent * torrent;
// Stop updating the interface
[fTimer invalidate];
[fUpdateTimer invalidate];
//clear badge
[fBadger clearBadge];
[fBadger release];
[fBadger release];
// Save history
[self updateTorrentHistory];
// Stop running torrents
for( i = 0; i < fCount; i++ )
if( fStat[i].status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD |
TR_STATUS_SEED ) )
tr_torrentStop( fHandle, i );
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
[torrent stop];
}
// Wait for torrents to stop (5 seconds timeout)
NSDate * start = [NSDate date];
while( fCount > 0 )
while( [fTorrents count] )
{
torrent = [fTorrents objectAtIndex: 0];
while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
!( fStat[0].status & TR_STATUS_PAUSE ) )
![torrent isPaused] )
{
usleep( 100000 );
tr_torrentStat( fHandle, &fStat );
[torrent update];
}
tr_torrentClose( fHandle, 0 );
fCount = tr_torrentStat( fHandle, &fStat );
[fTorrents removeObject: torrent];
[torrent release];
}
tr_close( fHandle );
}
- (void) showPreferenceWindow: (id) sender
@@ -257,22 +273,22 @@ static void sleepCallBack( void * controller, io_service_t y,
{
[fPrefsWindow center];
}
[fPrefsWindow makeKeyAndOrderFront:NULL];
}
- (void) folderChoiceClosed: (NSOpenPanel *) s returnCode: (int) code
contextInfo: (void *) info
{
Torrent * torrent = [fTorrents lastObject];
if (code == NSOKButton)
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[[s filenames] objectAtIndex: 0] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
[torrent setFolder: [[s filenames] objectAtIndex: 0]];
[torrent start];
}
else
{
tr_torrentClose( fHandle, tr_torrentCount( fHandle ) - 1 );
[torrent release];
}
[NSApp stopModal];
}
@@ -281,6 +297,7 @@ static void sleepCallBack( void * controller, io_service_t y,
openFiles: (NSArray *) filenames
{
NSString * downloadChoice, * downloadFolder, * torrentPath;
Torrent * torrent;
downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
downloadFolder = [fDefaults stringForKey: @"DownloadFolder"];
@@ -288,8 +305,10 @@ static void sleepCallBack( void * controller, io_service_t y,
NSEnumerator * enumerator = [filenames objectEnumerator];
while ((torrentPath = [enumerator nextObject]))
{
if( tr_torrentInit( fHandle, [torrentPath UTF8String] ) )
torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib];
if( !torrent )
continue;
[fTorrents addObject: torrent];
/* Add it to the "File > Open Recent" menu */
[[NSDocumentController sharedDocumentController]
@@ -297,20 +316,18 @@ static void sleepCallBack( void * controller, io_service_t y,
if( [downloadChoice isEqualToString: @"Constant"] )
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[downloadFolder stringByExpandingTildeInPath] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
[torrent setFolder: [downloadFolder stringByExpandingTildeInPath]];
[torrent start];
}
else if( [downloadChoice isEqualToString: @"Torrent"] )
{
tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1,
[[torrentPath stringByDeletingLastPathComponent] UTF8String] );
tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 );
[torrent setFolder: [torrentPath stringByDeletingLastPathComponent]];
[torrent start];
}
else
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
[panel setPrompt: @"Select Download Folder"];
[panel setMessage: [NSString stringWithFormat:
@"Select the download folder for %@",
@@ -355,7 +372,7 @@ static void sleepCallBack( void * controller, io_service_t y,
panel = [NSOpenPanel openPanel];
fileTypes = [NSArray arrayWithObject: @"torrent"];
[panel setAllowsMultipleSelection: YES];
[panel setCanChooseFiles: YES];
[panel setCanChooseDirectories: NO];
@@ -393,21 +410,21 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) resumeAllTorrents: (id) sender
{
int i;
for ( i = 0; i < fCount; i++)
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
if ( fStat[i].status & ( TR_STATUS_STOPPING
| TR_STATUS_PAUSE | TR_STATUS_STOPPED ) )
{
[self resumeTorrentWithIndex: i];
}
[torrent start];
}
[self updateUI: nil];
[self updateTorrentHistory];
}
- (void) resumeTorrentWithIndex: (int) idx
{
tr_torrentStart( fHandle, idx );
[self updateUI: NULL];
Torrent * torrent = [fTorrents objectAtIndex: idx];
[torrent start];
[self updateUI: nil];
[self updateTorrentHistory];
}
@@ -418,21 +435,21 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) stopAllTorrents: (id) sender
{
int i;
for ( i = 0; i < fCount; i++)
Torrent * torrent;
NSEnumerator * enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
if ( fStat[i].status & ( TR_STATUS_CHECK
| TR_STATUS_DOWNLOAD | TR_STATUS_SEED) )
{
[self stopTorrentWithIndex: i];
}
[torrent stop];
}
[self updateUI: nil];
[self updateTorrentHistory];
}
- (void) stopTorrentWithIndex: (int) idx
{
tr_torrentStop( fHandle, idx );
[self updateUI: NULL];
Torrent * torrent = [fTorrents objectAtIndex: idx];
[torrent stop];
[self updateUI: nil];
[self updateTorrentHistory];
}
@@ -440,73 +457,69 @@ static void sleepCallBack( void * controller, io_service_t y,
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData
{
if ( fStat[idx].status & ( TR_STATUS_CHECK
| TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
Torrent * torrent = [fTorrents objectAtIndex: idx];
if( [torrent isActive] && [fDefaults boolForKey: @"CheckRemove"] )
{
if ([fDefaults boolForKey: @"CheckRemove"])
{
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithFormat: @"%d", idx], @"Index",
[NSString stringWithFormat: @"%d", deleteTorrent], @"DeleteTorrent",
[NSString stringWithFormat: @"%d", deleteData], @"DeleteData",
nil];
[dict retain];
NSBeginAlertSheet(@"Confirm Remove",
@"Remove", @"Cancel", nil,
fWindow, self,
@selector(removeSheetDidEnd:returnCode:contextInfo:),
NULL, dict, @"This torrent is active. Do you really want to remove it?");
return;
}
//stop if not stopped
else
[self stopTorrentWithIndex:idx];
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithInt: idx], @"Index",
[NSString stringWithInt: deleteTorrent], @"DeleteTorrent",
[NSString stringWithInt: deleteData], @"DeleteData",
nil];
[dict retain];
NSBeginAlertSheet(@"Confirm Remove",
@"Remove", @"Cancel", nil, fWindow, self,
@selector(removeSheetDidEnd:returnCode:contextInfo:), NULL, dict,
@"This torrent is active. Do you really want to remove it?");
}
else
{
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: deleteTorrent
deleteData: deleteData];
}
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: deleteTorrent
deleteData: deleteData];
}
- (void) removeSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode
contextInfo:(NSDictionary *)dict
{
[NSApp stopModal];
if (returnCode != NSAlertDefaultReturn)
{
[dict release];
return;
}
int idx = [[dict objectForKey:@"Index"] intValue];
[self stopTorrentWithIndex:idx];
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
if( returnCode == NSAlertDefaultReturn )
{
int idx = [[dict objectForKey:@"Index"] intValue];
[self confirmRemoveTorrentWithIndex: idx
deleteTorrent: [[dict objectForKey:@"DeleteTorrent"] intValue]
deleteData: [[dict objectForKey:@"DeleteData"] intValue]];
}
[dict release];
}
- (void) confirmRemoveTorrentWithIndex: (int) idx
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData
{
Torrent * torrent = [fTorrents objectAtIndex: idx];
/* Pause if not paused already */
[torrent stop];
if( deleteData )
{
[self finderTrash: [NSString stringWithFormat: @"%@/%@",
[NSString stringWithUTF8String: fStat[idx].folder],
[NSString stringWithUTF8String: fStat[idx].info.name]]];
[torrent trashData];
}
if( deleteTorrent )
{
[self finderTrash: [NSString stringWithUTF8String:
fStat[idx].info.torrent]];
[torrent trashTorrent];
}
tr_torrentClose( fHandle, idx );
[self updateUI: NULL];
[fTorrents removeObject: torrent];
[torrent release];
[self updateUI: nil];
[self updateTorrentHistory];
}
@@ -545,24 +558,35 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) updateUI: (NSTimer *) t
{
float dl, ul;
int row, i;
NSEnumerator * enumerator;
Torrent * torrent;
//Update the NSTableView
if (fStat)
free(fStat);
fCount = tr_torrentStat( fHandle, &fStat );
fDownloading = 0;
fSeeding = 0;
[fTableView updateUI: fStat];
/* Update the TableView */
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
[torrent update];
if( [torrent justFinished] )
{
/* Notifications */
[self notifyGrowl: [torrent name]];
if( ![fWindow isKeyWindow] )
{
fCompleted++;
}
}
}
[fTableView reloadData];
//Update the global DL/UL rates
tr_torrentRates( fHandle, &dl, &ul );
tr_torrentRates( fLib, &dl, &ul );
NSString * downloadRate = [NSString stringForSpeed: dl];
NSString * uploadRate = [NSString stringForSpeed: ul];
[fTotalDLField setStringValue: downloadRate];
[fTotalULField setStringValue: uploadRate];
#if 0
//Update DL/UL totals in the Info panel
row = [fTableView selectedRow];
if( row >= 0 )
@@ -572,24 +596,7 @@ static void sleepCallBack( void * controller, io_service_t y,
[fInfoUploaded setStringValue:
[NSString stringForFileSize: fStat[row].uploaded]];
}
//check if torrents have recently ended.
for (i = 0; i < fCount; i++)
{
if (fStat[i].status & (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD))
fDownloading++;
else if (fStat[i].status & TR_STATUS_SEED)
fSeeding++;
if( !tr_getFinished( fHandle, i ) )
continue;
if( ![fWindow isKeyWindow] )
fCompleted++;
[self notifyGrowl: [NSString stringWithUTF8String:
fStat[i].info.name]];
tr_setFinished( fHandle, i, 0 );
}
#endif
//badge dock
[fBadger updateBadgeWithCompleted: fCompleted
@@ -601,51 +608,42 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) updateTorrentHistory
{
if( !fStat )
return;
NSMutableArray * history = [NSMutableArray
arrayWithCapacity: [fTorrents count]];
NSMutableArray * history = [NSMutableArray arrayWithCapacity: fCount];
int i;
for (i = 0; i < fCount; i++)
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent;
while( ( torrent = [enumerator nextObject] ) )
{
[history addObject: [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String: fStat[i].info.torrent],
@"TorrentPath",
[NSString stringWithUTF8String: tr_torrentGetFolder( fHandle, i )],
@"DownloadFolder",
fStat[i].status & (TR_STATUS_CHECK | TR_STATUS_DOWNLOAD |
TR_STATUS_SEED) ? @"NO" : @"YES",
@"Paused",
[torrent path], @"TorrentPath",
[torrent getFolder], @"DownloadFolder",
[torrent isActive] ? @"NO" : @"YES", @"Paused",
nil]];
}
[fDefaults setObject: history forKey: @"History"];
}
- (int) numberOfRowsInTableView: (NSTableView *) t
{
return fCount;
return [fTorrents count];
}
- (id) tableView: (NSTableView *) t objectValueForTableColumn:
(NSTableColumn *) tableColumn row: (int) rowIndex
{
return NULL;
return nil;
}
- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
{
BOOL w;
w = [fWindow isKeyWindow] && rowIndex == [fTableView selectedRow];
if( [[tableColumn identifier] isEqualToString: @"Name"] )
{
[(NameCell *) cell setStat: &fStat[rowIndex] whiteText: w];
}
else if( [[tableColumn identifier] isEqualToString: @"Progress"] )
{
[(ProgressCell *) cell setStat: &fStat[rowIndex] whiteText: w];
}
[cell setTorrent: [fTorrents objectAtIndex: rowIndex]];
[cell setTextColor: ( [fWindow isKeyWindow] &&
rowIndex == [fTableView selectedRow] ) ?
[NSColor whiteColor] : [NSColor blackColor]];
}
- (BOOL) tableView: (NSTableView *) t acceptDrop:
@@ -670,7 +668,8 @@ static void sleepCallBack( void * controller, io_service_t y,
count] == 0)
return NSDragOperationNone;
[fTableView setDropRow: fCount dropOperation: NSTableViewDropAbove];
[fTableView setDropRow: [fTorrents count]
dropOperation: NSTableViewDropAbove];
return NSDragOperationGeneric;
}
@@ -694,6 +693,7 @@ static void sleepCallBack( void * controller, io_service_t y,
return;
}
#if 0
/* Update info window */
[fInfoTitle setStringValue: [NSString stringWithUTF8String:
fStat[row].info.name]];
@@ -703,25 +703,26 @@ static void sleepCallBack( void * controller, io_service_t y,
fStat[row].info.trackerAnnounce]];
[fInfoSize setStringValue:
[NSString stringForFileSize: fStat[row].info.totalSize]];
[fInfoPieces setStringValue: [NSString stringWithFormat: @"%d",
[fInfoPieces setStringValue: [NSString stringWithInt:
fStat[row].info.pieceCount]];
[fInfoPieceSize setStringValue:
[NSString stringForFileSize: fStat[row].info.pieceSize]];
[fInfoFolder setStringValue: [[NSString stringWithUTF8String:
tr_torrentGetFolder( fHandle, row )] lastPathComponent]];
if ( fStat[row].seeders == -1 ) {
[fInfoSeeders setStringValue: [NSString stringWithUTF8String: "?"]];
} else {
[fInfoSeeders setStringValue: [NSString stringWithFormat: @"%d",
[fInfoSeeders setStringValue: [NSString stringWithInt:
fStat[row].seeders]];
}
if ( fStat[row].leechers == -1 ) {
[fInfoLeechers setStringValue: [NSString stringWithUTF8String: "?"]];
} else {
[fInfoLeechers setStringValue: [NSString stringWithFormat: @"%d",
[fInfoLeechers setStringValue: [NSString stringWithInt:
fStat[row].leechers]];
}
#endif
}
- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
@@ -824,16 +825,30 @@ static void sleepCallBack( void * controller, io_service_t y,
//enable remove item
if (action == @selector(removeTorrent:))
return [fTableView selectedRow] >= 0;
Torrent * torrent;
NSEnumerator * enumerator;
//enable resume all item
if (action == @selector(resumeAllTorrents:))
return fCount > fDownloading + fSeeding;
{
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
if( [torrent isPaused] )
return YES;
return NO;
}
//enable pause all item
if (action == @selector(stopAllTorrents:))
return fDownloading > 0 || fSeeding > 0;
{
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
if( [torrent isActive] )
return YES;
return NO;
}
return YES;
}
@@ -844,14 +859,14 @@ static void sleepCallBack( void * controller, io_service_t y,
//disable menus if customize sheet is active
if ([fToolbar customizationPaletteIsRunning])
return NO;
//enable customize toolbar item
if (action == @selector(showHideToolbar:))
{
[menuItem setTitle: [fToolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
return YES;
}
//enable show info
if (action == @selector(showInfo:))
{
@@ -859,16 +874,37 @@ static void sleepCallBack( void * controller, io_service_t y,
return YES;
}
Torrent * torrent;
NSEnumerator * enumerator;
//enable resume all item
if (action == @selector(resumeAllTorrents:))
return fCount > fDownloading + fSeeding;
{
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
if( [torrent isPaused] )
return YES;
return NO;
}
//enable pause all item
if (action == @selector(stopAllTorrents:))
return fDownloading > 0 || fSeeding > 0;
{
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
if( [torrent isActive] )
return YES;
return NO;
}
int row = [fTableView selectedRow];
torrent = ( row < 0 ) ? nil : [fTorrents objectAtIndex: row];
if (action == @selector(revealFromMenu:))
{
return ( torrent != nil );
}
//enable remove items
if (action == @selector(removeTorrent:)
|| action == @selector(removeTorrentDeleteFile:)
@@ -876,8 +912,8 @@ static void sleepCallBack( void * controller, io_service_t y,
|| action == @selector(removeTorrentDeleteBoth:))
{
//append or remove ellipsis when needed
if (row >= 0 && fStat[row].status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD)
&& [[fDefaults stringForKey: @"CheckRemove"] isEqualToString:@"YES"])
if( torrent && [torrent isActive] &&
[fDefaults boolForKey: @"CheckRemove"] )
{
if (![[menuItem title] hasSuffix:NS_ELLIPSIS])
[menuItem setTitle:[[menuItem title] stringByAppendingString:NS_ELLIPSIS]];
@@ -887,59 +923,59 @@ static void sleepCallBack( void * controller, io_service_t y,
if ([[menuItem title] hasSuffix:NS_ELLIPSIS])
[menuItem setTitle:[[menuItem title] substringToIndex:[[menuItem title] length]-[NS_ELLIPSIS length]]];
}
return row >= 0;
return ( torrent != nil );
}
//enable reveal in finder item
if (action == @selector(revealFromMenu:))
return row >= 0;
//enable and change pause / remove item
if (action == @selector(resumeTorrent:) || action == @selector(stopTorrent:))
if( action == @selector(resumeTorrent:) ||
action == @selector(stopTorrent:) )
{
if (row >= 0 && fStat[row].status & TR_STATUS_PAUSE)
{
[menuItem setTitle: @"Resume"];
[menuItem setAction: @selector( resumeTorrent: )];
}
else
if( !torrent || [torrent isActive] )
{
[menuItem setTitle: @"Pause"];
[menuItem setAction: @selector( stopTorrent: )];
}
return row >= 0;
else
{
[menuItem setTitle: @"Resume"];
[menuItem setAction: @selector( resumeTorrent: )];
}
return ( torrent != nil );
}
return YES;
}
- (void) sleepCallBack: (natural_t) messageType argument:
(void *) messageArgument
{
int i;
NSEnumerator * enumerator;;
Torrent * torrent;
switch( messageType )
{
case kIOMessageSystemWillSleep:
/* Close all connections before going to sleep and remember
we should resume when we wake up */
for( i = 0; i < fCount; i++ )
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
if( fStat[i].status & ( TR_STATUS_CHECK |
TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
[torrent sleep];
}
/* Wait for torrents to stop (5 seconds timeout) */
NSDate * start = [NSDate date];
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
![torrent isPaused] )
{
tr_torrentStop( fHandle, i );
fResumeOnWake[i] = 1;
}
else
{
fResumeOnWake[i] = 0;
usleep( 100000 );
[torrent update];
}
}
/* TODO: wait a few seconds to let the torrents
stop properly */
IOAllowPowerChange( fRootPort, (long) messageArgument );
break;
@@ -953,12 +989,10 @@ static void sleepCallBack( void * controller, io_service_t y,
case kIOMessageSystemHasPoweredOn:
/* Resume download after we wake up */
for( i = 0; i < fCount; i++ )
enumerator = [fTorrents objectEnumerator];
while( ( torrent = [enumerator nextObject] ) )
{
if( fResumeOnWake[i] )
{
tr_torrentStart( fHandle, i );
}
[torrent wakeUp];
}
break;
}
@@ -972,9 +1006,8 @@ static void sleepCallBack( void * controller, io_service_t y,
rectWin = [fWindow frame];
rectView = [fScrollView frame];
foo = 25.0 + MAX( 1, fCount ) * ( [fTableView rowHeight] +
[fTableView intercellSpacing].height ) -
rectView.size.height;
foo = 25.0 - rectView.size.height + MAX( 1U, [fTorrents count] ) *
( [fTableView rowHeight] + [fTableView intercellSpacing].height );
rectWin.size.height += foo;
rectWin.origin.y -= foo;
@@ -1007,7 +1040,7 @@ static void sleepCallBack( void * controller, io_service_t y,
if( !fHasGrowl )
return;
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"
" if exists application process \"GrowlHelperApp\" then\n"
@@ -1028,14 +1061,14 @@ static void sleepCallBack( void * controller, io_service_t y,
}
- (void) growlRegister: (id) sender
{
{
NSString * growlScript;
NSAppleScript * appleScript;
NSDictionary * error;
if( !fHasGrowl )
return;
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"
" if exists application process \"GrowlHelperApp\" then\n"
@@ -1047,7 +1080,7 @@ static void sleepCallBack( void * controller, io_service_t y,
" end tell\n"
" end if\n"
"end tell"];
appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
if( ![appleScript executeAndReturnError: &error] )
{
@@ -1058,52 +1091,9 @@ static void sleepCallBack( void * controller, io_service_t y,
- (void) revealFromMenu: (id) sender
{
int row = [fTableView selectedRow];
if (row >= 0)
{
[self finderReveal: [NSString stringWithFormat: @"%@/%@",
[NSString stringWithUTF8String: fStat[row].folder],
[NSString stringWithUTF8String: fStat[row].info.name]]];
}
}
- (void) finderReveal: (NSString *) path
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat:
@"tell application \"Finder\"\n"
" activate\n"
" reveal (POSIX file \"%@\")\n"
"end tell", path];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "finderReveal failed\n" );
}
[appleScript release];
}
- (void) finderTrash: (NSString *) path
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat:
@"tell application \"Finder\"\n"
" move (POSIX file \"%@\") to trash\n"
"end tell", path];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "finderTrash failed\n" );
}
[appleScript release];
Torrent * torrent;
torrent = [fTorrents objectAtIndex: [fTableView selectedRow]];
[torrent reveal];
}
- (void) checkForUpdate: (id) sender
@@ -1141,7 +1131,7 @@ static void sleepCallBack( void * controller, io_service_t y,
[self checkForUpdateAuto: YES];
[fDefaults setObject: [NSDate date] forKey: @"VersionCheckLast"];
}
- (void) checkForUpdateAuto: (BOOL) automatic
{
fCheckIsAutomatic = automatic;
@@ -1150,7 +1140,7 @@ static void sleepCallBack( void * controller, io_service_t y,
}
- (void) URLResourceDidFinishLoading: (NSURL *) sender
{
{
NSDictionary * dict = [NSPropertyListSerialization
propertyListFromData: [sender resourceDataUsingCache: NO]
mutabilityOption: NSPropertyListImmutable

View File

@@ -15,7 +15,7 @@
<key>589</key>
<string>54 521 112 118 0 0 1152 842 </string>
<key>783</key>
<string>483 459 420 250 0 0 1440 878 </string>
<string>411 429 420 250 0 0 1280 832 </string>
<key>796</key>
<string>484 520 420 129 0 0 1440 878 </string>
<key>825</key>
@@ -26,6 +26,6 @@
<key>IBOldestOS</key>
<integer>3</integer>
<key>IBSystem Version</key>
<string>8F1111g</string>
<string>8H14</string>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,168 +0,0 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "NameCell.h"
#import "StringAdditions.h"
#import "Utils.h"
@implementation NameCell
- (id) init
{
if ((self = [super init]))
fIcons = [[NSMutableDictionary alloc] initWithCapacity: 10];
return self;
}
- (void) dealloc
{
[fIcons release];
[super dealloc];
}
- (NSImage *) iconForFileType: (NSString *) type
{
NSImage * icon;
if (!(icon = [fIcons objectForKey: type]))
{
/* Unknown file type, get its icon and cache it */
icon = [[NSWorkspace sharedWorkspace] iconForFileType: type];
[icon setFlipped: YES];
[fIcons setObject: icon forKey: type];
}
return icon;
}
- (void) setStat: (tr_stat_t *) stat whiteText: (BOOL) w
{
fWhiteText = w;
fNameString = [NSString stringWithUTF8String: stat->info.name];
fSizeString = [NSString stringWithFormat: @" (%@)",
[NSString stringForFileSize: stat->info.totalSize]];
fCurrentIcon = [self iconForFileType: stat->info.fileCount > 1 ?
NSFileTypeForHFSTypeCode('fldr') : [fNameString pathExtension]];
fTimeString = @"";
fPeersString = @"";
if( stat->status & TR_STATUS_PAUSE )
{
fTimeString = [NSString stringWithFormat:
@"Paused (%.2f %%)", 100 * stat->progress];
}
else if( stat->status & TR_STATUS_CHECK )
{
fTimeString = [NSString stringWithFormat:
@"Checking existing files (%.2f %%)", 100 * stat->progress];
}
else if( stat->status & TR_STATUS_DOWNLOAD )
{
if( stat->eta < 0 )
{
fTimeString = [NSString stringWithFormat:
@"Finishing in --:--:-- (%.2f %%)", 100 * stat->progress];
}
else
{
fTimeString = [NSString stringWithFormat:
@"Finishing in %02d:%02d:%02d (%.2f %%)",
stat->eta / 3600, ( stat->eta / 60 ) % 60,
stat->eta % 60, 100 * stat->progress];
}
fPeersString = [NSString stringWithFormat:
@"Downloading from %d of %d peer%s",
stat->peersUploading, stat->peersTotal,
( stat->peersTotal == 1 ) ? "" : "s"];
}
else if( stat->status & TR_STATUS_SEED )
{
fTimeString = [NSString stringWithFormat:
@"Seeding, uploading to %d of %d peer%s",
stat->peersDownloading, stat->peersTotal,
( stat->peersTotal == 1 ) ? "" : "s"];
}
else if( stat->status & TR_STATUS_STOPPING )
{
fTimeString = @"Stopping...";
}
if( ( stat->status & ( TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) &&
( stat->status & TR_TRACKER_ERROR ) )
{
fPeersString = [NSString stringWithFormat: @"%@%@",
@"Error: ", [NSString stringWithUTF8String: stat->error]];
}
}
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view
{
NSString * string;
NSPoint pen;
NSMutableDictionary * attributes;
if( ![view lockFocusIfCanDraw] )
{
return;
}
pen = cellFrame.origin;
float cellWidth = cellFrame.size.width;
pen.x += 5;
pen.y += 5;
[fCurrentIcon drawAtPoint: pen fromRect:
NSMakeRect(0,0,[fCurrentIcon size].width,[fCurrentIcon size].height)
operation: NSCompositeSourceOver fraction: 1.0];
attributes = [NSMutableDictionary dictionaryWithCapacity: 2];
[attributes setObject: fWhiteText ? [NSColor whiteColor] :
[NSColor blackColor] forKey: NSForegroundColorAttributeName];
[attributes setObject: [NSFont messageFontOfSize: 12.0]
forKey: NSFontAttributeName];
pen.x += 37;
string = [[fNameString stringFittingInWidth: cellWidth -
72 - [fSizeString sizeWithAttributes: attributes].width
withAttributes: attributes] stringByAppendingString: fSizeString];
[string drawAtPoint: pen withAttributes: attributes];
[attributes setObject: [NSFont messageFontOfSize: 10.0]
forKey: NSFontAttributeName];
pen.x += 5; pen.y += 20;
[fTimeString drawAtPoint: pen withAttributes: attributes];
pen.x += 0; pen.y += 15;
string = [fPeersString stringFittingInWidth: cellFrame.size.width -
77 withAttributes: attributes];
[string drawAtPoint: pen withAttributes: attributes];
[view unlockFocus];
}
@end

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -21,6 +21,7 @@
*****************************************************************************/
#import "PrefsController.h"
#import "StringAdditions.h"
#import "Utils.h"
#define MIN_PORT 1
@@ -202,7 +203,7 @@
//if value entered is not an int or is not in range do not change
if (![[fPortField stringValue] isEqualToString:
[NSString stringWithFormat: @"%d", bindPort]]
[NSString stringWithInt: bindPort]]
|| bindPort < MIN_PORT
|| bindPort > MAX_PORT)
{
@@ -233,8 +234,7 @@
//if value entered is not an int or is less than 0 do not change
if (![[fUploadField stringValue] isEqualToString:
[NSString stringWithFormat: @"%d", uploadLimit]]
|| uploadLimit < 0)
[NSString stringWithInt: uploadLimit]] || uploadLimit < 0)
{
NSBeep();
uploadLimit = [fDefaults integerForKey: @"UploadLimit"];

View File

@@ -1,312 +0,0 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "ProgressCell.h"
#import "StringAdditions.h"
@implementation ProgressCell
/***********************************************************************
* Static tables
***********************************************************************
* We use these tables to optimize the drawing. They contain packed
* RGBA pixels for every color we might need.
**********************************************************************/
#if 0
/* Coefficients for the "3D effect" */
static float kBarCoeffs[] =
{ 0.49, 0.73, 0.84, 0.85, 0.84, 0.79, 0.78,
0.82, 0.86, 0.91, 0.93, 0.95, 0.96, 0.71 };
#endif
/* 255, 100, 80 */
static uint32_t kRed[] =
{ 0x7C3127FF, 0xBA493AFF, 0xD65443FF, 0xD85544FF, 0xD65443FF,
0xC94F3FFF, 0xC64E3EFF, 0xD15241FF, 0xDB5644FF, 0xE85B48FF,
0xED5D4AFF, 0xF25F4CFF, 0xF4604CFF, 0xB54738FF };
/* 160, 220, 255 */
static uint32_t kBlue1[] =
{ 0x4E6B7CFF, 0x74A0BAFF, 0x86B8D6FF, 0x88BBD8FF, 0x86B8D6FF,
0x7EADC9FF, 0x7CABC6FF, 0x83B4D1FF, 0x89BDDBFF, 0x91C8E8FF,
0x94CCEDFF, 0x98D1F2FF, 0x99D3F4FF, 0x719CB5FF };
/* 120, 190, 255 */
static uint32_t kBlue2[] =
{ 0x3A5D7CFF, 0x578ABAFF, 0x649FD6FF, 0x66A1D8FF, 0x649FD6FF,
0x5E96C9FF, 0x5D94C6FF, 0x629BD1FF, 0x67A3DBFF, 0x6DACE8FF,
0x6FB0EDFF, 0x72B4F2FF, 0x73B6F4FF, 0x5586B5FF };
/* 80, 160, 255 */
static uint32_t kBlue3[] =
{ 0x274E7CFF, 0x3A74BAFF, 0x4386D6FF, 0x4488D8FF, 0x4386D6FF,
0x3F7EC9FF, 0x3E7CC6FF, 0x4183D1FF, 0x4489DBFF, 0x4891E8FF,
0x4A94EDFF, 0x4C98F2FF, 0x4C99F4FF, 0x3871B5FF };
/* 30, 70, 180 */
static uint32_t kBlue4[] =
{ 0x0E2258FF, 0x153383FF, 0x193A97FF, 0x193B99FF, 0x193A97FF,
0x17378EFF, 0x17368CFF, 0x183993FF, 0x193C9AFF, 0x1B3FA3FF,
0x1B41A7FF, 0x1C42ABFF, 0x1C43ACFF, 0x15317FFF };
/* 130, 130, 130 */
static uint32_t kGray[] =
{ 0x3F3F3FFF, 0x5E5E5EFF, 0x6D6D6DFF, 0x6E6E6EFF, 0x6D6D6DFF,
0x666666FF, 0x656565FF, 0x6A6A6AFF, 0x6F6F6FFF, 0x767676FF,
0x787878FF, 0x7B7B7BFF, 0x7C7C7CFF, 0x5C5C5CFF };
/* 0, 255, 0 */
static uint32_t kGreen[] =
{ 0x007C00FF, 0x00BA00FF, 0x00D600FF, 0x00D800FF, 0x00D600FF,
0x00C900FF, 0x00C600FF, 0x00D100FF, 0x00DB00FF, 0x00E800FF,
0x00ED00FF, 0x00F200FF, 0x00F400FF, 0x00B500FF };
/***********************************************************************
* init
***********************************************************************
* Prepares the NSBitmapImageReps we are going to need in order to
* draw.
**********************************************************************/
- (id) init
{
self = [super init];
/* Load the background image for the progress bar and get it as a
32-bit bitmap */
fBackgroundBmp = [[[NSImage imageNamed: @"Progress.png"]
representations] objectAtIndex: 0];
/* Allocate another bitmap of the same size. We will draw the
progress bar in it */
fProgressBmp = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes: NULL pixelsWide:
[fBackgroundBmp size].width pixelsHigh:
[fBackgroundBmp size].height bitsPerSample: 8
samplesPerPixel: 4 hasAlpha: YES isPlanar: NO
colorSpaceName: NSCalibratedRGBColorSpace
bytesPerRow: 0 bitsPerPixel: 0];
return self;
}
/***********************************************************************
* setStat
***********************************************************************
* Readies ourselves to draw updated info.
**********************************************************************/
- (void) setStat: (tr_stat_t *) stat whiteText: (BOOL) w
{
int i;
uint8_t * in, * out;
fStat = stat;
fWhiteText = w;
/* Update the strings to be displayed */
if( fStat->progress == 1.0 )
fDlString = [@"Ratio: " stringByAppendingString:
[NSString stringForRatio: fStat->downloaded
upload: fStat->uploaded]];
else
fDlString = [@"DL: " stringByAppendingString:
[NSString stringForSpeed: fStat->rateDownload]];
fUlString = [@"UL: " stringByAppendingString:
[NSString stringForSpeed: fStat->rateUpload]];
/* Reset our bitmap to the background image... */
in = [fBackgroundBmp bitmapData];
out = [fProgressBmp bitmapData];
for( i = 0; i < [fProgressBmp size].height; i++ )
{
memcpy( out, in, [fProgressBmp size].width * 4 );
in += [fBackgroundBmp bytesPerRow];
out += [fProgressBmp bytesPerRow];
}
/* ...and redraw the progress bar on the top of it */
if( [[NSUserDefaults standardUserDefaults]
boolForKey:@"UseAdvancedBar"])
{
[self buildAdvancedBar];
}
else
{
[self buildSimpleBar];
}
}
/***********************************************************************
* buildSimpleBar
**********************************************************************/
- (void) buildSimpleBar
{
int h, w, end, pixelsPerRow;
uint32_t * p;
uint32_t * colors;
pixelsPerRow = [fProgressBmp bytesPerRow] / 4;
/* The background image is 124*18 pixels, but the actual
progress bar is 120*14 : the first two columns, the last
two columns and the last four lines contain the shadow. */
p = (uint32_t *) [fProgressBmp bitmapData] + 2;
end = lrintf( floor( fStat->progress * 120 ) );
if( fStat->status & TR_STATUS_SEED )
colors = kGreen;
else if( fStat->status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD ) )
colors = kBlue2;
else
colors = kGray;
for( h = 0; h < 14; h++ )
{
for( w = 0; w < end; w++ )
{
p[w] = htonl( colors[h] );
}
p += pixelsPerRow;
}
}
/***********************************************************************
* buildAdvancedBar
**********************************************************************/
- (void) buildAdvancedBar
{
int h, w, end, pixelsPerRow;
uint32_t * p;
uint32_t * colors;
if( fStat->status & TR_STATUS_SEED )
{
/* All green, same as the simple bar */
[self buildSimpleBar];
return;
}
pixelsPerRow = [fProgressBmp bytesPerRow] / 4;
/* First two lines: dark blue to show progression */
p = (uint32_t *) [fProgressBmp bitmapData];
p += 2;
end = lrintf( floor( fStat->progress * 120 ) );
for( h = 0; h < 2; h++ )
{
for( w = 0; w < end; w++ )
{
p[w] = htonl( kBlue4[h] );
}
p += pixelsPerRow;
}
/* Lines 2 to 14: blue or grey depending on whether
we have the piece or not */
for( w = 0; w < 120; w++ )
{
/* Point to pixel ( 2 + w, 2 ). We will then draw
"vertically" */
p = (uint32_t *) ( [fProgressBmp bitmapData] +
2 * [fProgressBmp bytesPerRow] );
p += 2 + w;
if( fStat->pieces[w] < 0 )
{
colors = kGray;
}
else if( fStat->pieces[w] < 1 )
{
colors = kRed;
}
else if( fStat->pieces[w] < 2 )
{
colors = kBlue1;
}
else if( fStat->pieces[w] < 3 )
{
colors = kBlue2;
}
else
{
colors = kBlue3;
}
for( h = 2; h < 14; h++ )
{
p[0] = htonl( colors[h] );
p += pixelsPerRow;
}
}
}
/***********************************************************************
* drawWithFrame
***********************************************************************
* We have the strings, we have the bitmap. Let's just draw them where
* they belong.
**********************************************************************/
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view
{
NSImage * img;
NSMutableDictionary * attributes;
NSPoint pen;
if( ![view lockFocusIfCanDraw] )
{
return;
}
pen = cellFrame.origin;
/* Init an NSImage with our bitmap in order to draw it. We need to
do this every time, or for some reason it won't draw if the
display is set to thousands of colors when Transmission was
started */
img = [[NSImage alloc] initWithSize: [fProgressBmp size]];
[img addRepresentation: fProgressBmp];
[img setFlipped: YES];
/* Actually draw the bar */
pen.x += 5; pen.y += 5;
[img drawAtPoint: pen fromRect: NSMakeRect( 0, 0,
[fProgressBmp size].width, [fProgressBmp size].height )
operation: NSCompositeSourceOver fraction: 1.0];
[img release];
/* Draw the strings with font 10 */
attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont messageFontOfSize: 10.0],
NSFontAttributeName,
fWhiteText ? [NSColor whiteColor] : [NSColor blackColor],
NSForegroundColorAttributeName,
NULL];
pen.x += 5; pen.y += 20;
[fDlString drawAtPoint: pen withAttributes: attributes];
pen.x += 0; pen.y += 15;
[fUlString drawAtPoint: pen withAttributes: attributes];
[view unlockFocus];
}
@end

View File

@@ -1,15 +1,30 @@
//
// StringAdditions.h
// Transmission
//
// Created by Mitchell Livingston on 1/16/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import <Cocoa/Cocoa.h>
@interface NSString (StringAdditions)
+ (NSString *) stringWithInt: (int) value;
+ (NSString *) stringForFileSize: (uint64_t) size;
+ (NSString *) stringForSpeed: (float) speed;
+ (NSString *) stringForSpeedAbbrev: (float) speed;

View File

@@ -1,16 +1,35 @@
//
// StringAdditions.m
// Transmission
//
// Created by Mitchell Livingston on 1/16/06.
// Copyright 2006 __MyCompanyName__. All rights reserved.
//
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "StringAdditions.h"
#import "Utils.h"
@implementation NSString (StringAdditions)
+ (NSString *) stringWithInt: (int) value
{
return [NSString stringWithFormat: @"%d", value];
}
+ (NSString *) stringForFileSize: (uint64_t) size
{
if (size < 1024)

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -20,28 +20,51 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef PROGRESSCELL_H
#define PROGRESSCELL_H
#import <Cocoa/Cocoa.h>
#import <transmission.h>
@interface ProgressCell : NSCell
@interface Torrent : NSObject
{
tr_stat_t * fStat;
BOOL fWhiteText;
tr_handle_t * fLib;
tr_torrent_t * fHandle;
tr_info_t * fInfo;
tr_stat_t * fStat;
BOOL fResumeOnWake;
NSString * fDlString;
NSString * fUlString;
NSBitmapImageRep * fBackgroundBmp;
NSBitmapImageRep * fProgressBmp;
NSImage * fIcon;
NSMutableString * fStatusString;
NSMutableString * fInfoString;
NSMutableString * fDownloadString;
NSMutableString * fUploadString;
}
- (id) init;
- (void) setStat: (tr_stat_t *) stat whiteText: (BOOL) w;
- (void) buildSimpleBar;
- (void) buildAdvancedBar;
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view;
@end
#endif
- (id) initWithPath: (NSString *) path lib: (tr_handle_t *) lib;
- (void) setFolder: (NSString *) path;
- (NSString *) getFolder;
- (void) getAvailability: (int8_t *) tab size: (int) size;
- (void) update;
- (void) start;
- (void) stop;
- (void) sleep;
- (void) wakeUp;
- (void) reveal;
- (void) trashTorrent;
- (void) trashData;
- (NSImage *) icon;
- (NSString *) path;
- (NSString *) name;
- (uint64_t) size;
- (float) progress;
- (BOOL) isActive;
- (BOOL) isSeeding;
- (BOOL) isPaused;
- (BOOL) justFinished;
- (NSString *) statusString;
- (NSString *) infoString;
- (NSString *) downloadString;
- (NSString *) uploadString;
@end

319
macosx/Torrent.m Normal file
View File

@@ -0,0 +1,319 @@
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "Torrent.h"
#import "StringAdditions.h"
@interface Torrent (Private)
- (void) trashPath: (NSString *) path;
@end
@implementation Torrent
- (id) initWithPath: (NSString *) path lib: (tr_handle_t *) lib
{
fLib = lib;
int error;
fHandle = tr_torrentInit( fLib, [path UTF8String], &error );
if( !fHandle )
{
[self release];
return nil;
}
fInfo = tr_torrentInfo( fHandle );
NSString * fileType = ( fInfo->fileCount > 1 ) ?
NSFileTypeForHFSTypeCode('fldr') : [[self name] pathExtension];
fIcon = [[NSWorkspace sharedWorkspace] iconForFileType: fileType];
[fIcon setFlipped: YES];
[fIcon retain];
fStatusString = [[NSMutableString alloc] initWithCapacity: 50];
fInfoString = [[NSMutableString alloc] initWithCapacity: 50];
fDownloadString = [[NSMutableString alloc] initWithCapacity: 10];
fUploadString = [[NSMutableString alloc] initWithCapacity: 10];
[self update];
return self;
}
- (void) dealloc
{
if( fHandle )
{
tr_torrentClose( fLib, fHandle );
[fIcon release];
[fStatusString release];
[fInfoString release];
[fDownloadString release];
[fUploadString release];
}
[super dealloc];
}
- (void) setFolder: (NSString *) path
{
tr_torrentSetFolder( fHandle, [path UTF8String] );
}
- (NSString *) getFolder
{
return [NSString stringWithUTF8String: tr_torrentGetFolder( fHandle )];
}
- (void) getAvailability: (int8_t *) tab size: (int) size
{
tr_torrentAvailability( fHandle, tab, size );
}
- (void) update
{
fStat = tr_torrentStat( fHandle );
[fStatusString setString: @""];
[fInfoString setString: @""];
switch( fStat->status )
{
case TR_STATUS_PAUSE:
[fStatusString appendFormat: @"Paused (%.2f %%)",
100 * fStat->progress];
break;
case TR_STATUS_CHECK:
[fStatusString appendFormat:
@"Checking existing files (%.2f %%)",
100 * fStat->progress];
break;
case TR_STATUS_DOWNLOAD:
if( fStat->eta < 0 )
{
[fStatusString appendFormat:
@"Finishing in --:--:-- (%.2f %%)",
100 * fStat->progress];
}
else
{
[fStatusString appendFormat:
@"Finishing in %02d:%02d:%02d (%.2f %%)",
fStat->eta / 3600, ( fStat->eta / 60 ) % 60,
fStat->eta % 60, 100 * fStat->progress];
}
[fInfoString appendFormat:
@"Downloading from %d of %d peer%s",
fStat->peersUploading, fStat->peersTotal,
( fStat->peersTotal == 1 ) ? "" : "s"];
break;
case TR_STATUS_SEED:
[fStatusString appendFormat:
@"Seeding, uploading to %d of %d peer%s",
fStat->peersDownloading, fStat->peersTotal,
( fStat->peersTotal == 1 ) ? "" : "s"];
break;
case TR_STATUS_STOPPING:
[fStatusString setString: @"Stopping..."];
break;
}
#if 0
if( ( stat->status & ( TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) &&
( stat->status & TR_TRACKER_ERROR ) )
{
fPeersString = [NSString stringWithFormat: @"%@%@",
@"Error: ", [NSString stringWithUTF8String: stat->error]];
}
#endif
[fUploadString setString: @""];
if( fStat->progress == 1.0 )
{
[fDownloadString setString: @"Ratio: "];
[fDownloadString appendString: [NSString stringForRatio:
fStat->downloaded upload: fStat->uploaded]];
}
else
{
[fDownloadString setString: @"DL: "];
[fDownloadString appendString: [NSString stringForSpeed:
fStat->rateDownload]];
}
[fUploadString setString: @"UL: "];
[fUploadString appendString: [NSString stringForSpeed:
fStat->rateUpload]];
}
- (void) start
{
if( fStat->status & TR_STATUS_INACTIVE )
{
tr_torrentStart( fHandle );
}
}
- (void) stop
{
if( fStat->status & TR_STATUS_ACTIVE )
{
tr_torrentStop( fHandle );
}
}
- (void) sleep
{
if( fStat->status & TR_STATUS_ACTIVE )
{
[self stop];
fResumeOnWake = YES;
}
else
{
fResumeOnWake = NO;
}
}
- (void) wakeUp
{
if( fResumeOnWake )
{
[self start];
}
}
- (void) reveal
{
NSString * path = [NSString stringWithFormat: @"%@/%@",
[self getFolder], [self name]];
NSURL * url = [NSURL fileURLWithPath: path];
[[NSWorkspace sharedWorkspace] selectFile: [url path]
inFileViewerRootedAtPath: nil];
}
- (void) trashTorrent
{
[self trashPath: [self path]];
}
- (void) trashData
{
[self trashPath: [NSString stringWithFormat: @"%@/%@",
[self getFolder], [self name]]];
}
- (NSImage *) icon
{
return fIcon;
}
- (NSString *) path
{
return [NSString stringWithUTF8String: fInfo->torrent];
}
- (NSString *) name
{
return [NSString stringWithUTF8String: fInfo->name];
}
- (uint64_t) size
{
return fInfo->totalSize;
}
- (float) progress
{
return fStat->progress;
}
- (BOOL) isActive
{
return ( fStat->status & TR_STATUS_ACTIVE );
}
- (BOOL) isSeeding
{
return ( fStat->status == TR_STATUS_SEED );
}
- (BOOL) isPaused
{
return ( fStat->status == TR_STATUS_PAUSE );
}
- (BOOL) justFinished
{
return tr_getFinished( fHandle );
}
- (NSString *) statusString
{
return fStatusString;
}
- (NSString *) infoString
{
return fInfoString;
}
- (NSString *) downloadString
{
return fDownloadString;
}
- (NSString *) uploadString
{
return fUploadString;
}
@end
@implementation Torrent (Private)
- (void) trashPath: (NSString *) path
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat:
@"tell application \"Finder\"\n"
" move (POSIX file \"%@\") to trash\n"
"end tell", path];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "trashPath failed\n" );
}
[appleScript release];
}
@end

View File

@@ -1,5 +1,5 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -20,26 +20,23 @@
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#ifndef NAMECELL_H
#define NAMECELL_H
#ifndef TORRENTCELL_H
#define TORRENTCELL_H
#import <Cocoa/Cocoa.h>
#import <transmission.h>
#import "Controller.h"
#import "Torrent.h"
@interface NameCell : NSCell
@interface TorrentCell : NSCell
{
BOOL fWhiteText;
Torrent * fTorrent;
NSColor * fTextColor;
NSString * fNameString;
NSString * fSizeString;
NSString * fTimeString;
NSString * fPeersString;
NSMutableDictionary * fIcons;
NSImage * fCurrentIcon;
NSBitmapImageRep * fBitmap;
int fWidth;
int8_t * fPieces;
}
- (void) setStat: (tr_stat_t *) stat whiteText: (BOOL) w;
- (void) setTorrent: (Torrent *) torrent;
- (void) setTextColor: (NSColor *) color;
@end
#endif

329
macosx/TorrentCell.m Normal file
View File

@@ -0,0 +1,329 @@
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "TorrentCell.h"
#import "StringAdditions.h"
#define BAR_HEIGHT 12
@implementation TorrentCell
/***********************************************************************
* Static tables
***********************************************************************
* We use these tables to optimize the drawing. They contain packed
* RGBA pixels for every color we might need.
**********************************************************************/
static uint32_t kBorder[] =
{ 0x00000005, 0x00000010, 0x00000015, 0x00000015,
0x00000015, 0x00000015, 0x00000015, 0x00000015,
0x00000015, 0x00000015, 0x00000010, 0x00000005 };
static uint32_t kBack[] =
{ 0xB4B4B4FF, 0xE3E3E3FF, 0xE8E8E8FF, 0xDEDEDEFF,
0xDBDBDBFF, 0xE5E5E5FF, 0xE7E7E7FF, 0xF5F5F5FF,
0xFAFAFAFF, 0xDEDEDEFF, 0x0000003F, 0x00000015 };
/* Coefficients for the "3D effect":
0.59, 0.91, 0.97, 0.92, 0.79, 0.76, 0.85, 0.93, 1.00, 0.99 */
/* 255, 100, 80 */
static uint32_t kRed[] =
{ 0x963A2FFF, 0xE85B48FF, 0xF7614DFF, 0xEA5C49FF,
0xC94F3FFF, 0xC14C3CFF, 0xD85544FF, 0xED5D4AFF,
0xFF6450FF, 0xFC634FFF, 0x0000003F, 0x00000015 };
/* 160, 220, 255 */
static uint32_t kBlue1[] =
{ 0x5E8196FF, 0x91C8E8FF, 0x9BD5F7FF, 0x93CAEAFF,
0x7EADC9FF, 0x79A7C1FF, 0x88BBD8FF, 0x94CCEDFF,
0xA0DCFFFF, 0x9ED9FCFF, 0x0000003F, 0x00000015 };
/* 120, 190, 255 */
static uint32_t kBlue2[] =
{ 0x467096FF, 0x6DACE8FF, 0x74B8F7FF, 0x6EAEEAFF,
0x5E96C9FF, 0x5B90C1FF, 0x66A1D8FF, 0x6FB0EDFF,
0x78BEFFFF, 0x76BCFCFF, 0x0000003F, 0x00000015 };
/* 80, 160, 255 */
static uint32_t kBlue3[] =
{ 0x2F5E96FF, 0x4891E8FF, 0x4D9BF7FF, 0x4993EAFF,
0x3F7EC9FF, 0x3C79C1FF, 0x4488D8FF, 0x4A94EDFF,
0x50A0FFFF, 0x4F9EFCFF, 0x0000003F, 0x00000015 };
/* 30, 70, 180 */
static uint32_t kBlue4[] =
{ 0x11296AFF, 0x1B3FA3FF, 0x1D43AEFF, 0x1B40A5FF,
0x17378EFF, 0x163588FF, 0x193B99FF, 0x1B41A7FF,
0x1E46B4FF, 0x1D45B2FF, 0x0000003F, 0x00000015 };
/* 130, 130, 130 */
static uint32_t kGray[] =
{ 0x4C4C4CFF, 0x767676FF, 0x7E7E7EFF, 0x777777FF,
0x666666FF, 0x626262FF, 0x6E6E6EFF, 0x787878FF,
0x828282FF, 0x808080FF, 0x0000003F, 0x00000015 };
/* 0, 255, 0 */
static uint32_t kGreen[] =
{ 0x009600FF, 0x00E800FF, 0x00F700FF, 0x00EA00FF,
0x00C900FF, 0x00C100FF, 0x00D800FF, 0x00ED00FF,
0x00FF00FF, 0x00FC00FF, 0x0000003F, 0x00000015 };
- (void) setTorrent: (Torrent *) torrent
{
fTorrent = torrent;
}
- (void) setTextColor: (NSColor *) color
{
fTextColor = color;
}
/***********************************************************************
* init
***********************************************************************
* Prepares the NSBitmapImageReps we are going to need in order to
* draw.
**********************************************************************/
- (id) init
{
self = [super init];
return self;
}
/***********************************************************************
* buildSimpleBar
**********************************************************************/
- (void) buildSimpleBar
{
int h, w, end, pixelsPerRow;
uint32_t * p;
uint32_t * colors;
pixelsPerRow = [fBitmap bytesPerRow] / 4;
p = (uint32_t *) [fBitmap bitmapData] + 1;
end = lrintf( floor( [fTorrent progress] * ( fWidth - 2 ) ) );
if( [fTorrent isSeeding] )
colors = kGreen;
else if( [fTorrent isActive] )
colors = kBlue2;
else
colors = kGray;
for( h = 0; h < BAR_HEIGHT; h++ )
{
for( w = 0; w < end; w++ )
{
p[w] = htonl( colors[h] );
}
for( w = end; w < fWidth - 2; w++ )
{
p[w] = htonl( kBack[h] );
}
p += pixelsPerRow;
}
}
/***********************************************************************
* buildAdvancedBar
**********************************************************************/
- (void) buildAdvancedBar
{
int h, w, end, pixelsPerRow;
uint32_t * p, * colors;
uint8_t * bitmapData = [fBitmap bitmapData];
int bytesPerRow = [fBitmap bytesPerRow];
fPieces = malloc( fWidth );
[fTorrent getAvailability: fPieces size: fWidth];
if( [fTorrent isSeeding] )
{
/* All green, same as the simple bar */
[self buildSimpleBar];
return;
}
pixelsPerRow = [fBitmap bytesPerRow] / 4;
/* First two lines: dark blue to show progression */
end = lrintf( floor( [fTorrent progress] * ( fWidth - 2 ) ) );
for( h = 0; h < 2; h++ )
{
p = (uint32_t *) ( bitmapData + h * bytesPerRow ) + 1;
for( w = 0; w < end; w++ )
{
p[w] = htonl( kBlue4[h] );
}
for( w = end; w < fWidth - 2; w++ )
{
p[w] = htonl( kBack[h] );
}
}
/* Lines 2 to 14: blue or grey depending on whether
we have the piece or not */
for( w = 0; w < fWidth - 2; w++ )
{
/* Point to pixel ( 2 + w, 2 ). We will then draw
"vertically" */
p = (uint32_t *) ( bitmapData + 2 * bytesPerRow ) + 1 + w;
if( fPieces[w] < 0 )
{
colors = kGray;
}
else if( fPieces[w] < 1 )
{
colors = kRed;
}
else if( fPieces[w] < 2 )
{
colors = kBlue1;
}
else if( fPieces[w] < 3 )
{
colors = kBlue2;
}
else
{
colors = kBlue3;
}
for( h = 2; h < BAR_HEIGHT; h++ )
{
p[0] = htonl( colors[h] );
p = (uint32_t *) ( (uint8_t *) p + bytesPerRow );
}
}
free( fPieces );
}
- (void) buildBar
{
int h;
uint32_t * p;
/* Left and right borders */
p = (uint32_t *) [fBitmap bitmapData];
for( h = 0; h < BAR_HEIGHT; h++ )
{
p[0] = htonl( kBorder[h] );
p[fWidth - 1] = htonl( kBorder[h] );
p += [fBitmap bytesPerRow] / 4;
}
/* ...and redraw the progress bar on the top of it */
if( [[NSUserDefaults standardUserDefaults]
boolForKey:@"UseAdvancedBar"])
{
[self buildAdvancedBar];
}
else
{
[self buildSimpleBar];
}
}
/***********************************************************************
* drawWithFrame
***********************************************************************
* We have the strings, we have the bitmap. Let's just draw them where
* they belong.
**********************************************************************/
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view
{
if( ![view lockFocusIfCanDraw] )
{
return;
}
NSMutableDictionary * attributes;
attributes = [NSMutableDictionary dictionaryWithCapacity: 2];
[attributes setObject: fTextColor
forKey: NSForegroundColorAttributeName];
NSPoint pen = cellFrame.origin;
/* Draw the icon */
pen.x += 5; pen.y += 10;
NSImage * icon = [fTorrent icon];
[icon drawAtPoint: pen fromRect:
NSMakeRect( 0, 0, [icon size].width, [icon size].height )
operation: NSCompositeSourceOver fraction: 1.0];
NSString * string;
fWidth = NSWidth( cellFrame ) - [icon size].width - 15;
/* Draw file or folder name */
pen.x += [icon size].width + 5; pen.y -= 7;
[attributes setObject: [NSFont messageFontOfSize: 11]
forKey: NSFontAttributeName];
NSString * sizeString = [NSString stringWithFormat: @" (%@)",
[NSString stringForFileSize: [fTorrent size]]];
string = [[[fTorrent name] stringFittingInWidth: fWidth - 50 -
[sizeString sizeWithAttributes: attributes].width
withAttributes: attributes] stringByAppendingString: sizeString];
[string drawAtPoint: pen withAttributes: attributes];
/* Draw the progress bar */
pen.y += 17;
fBitmap = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes: nil pixelsWide: fWidth
pixelsHigh: BAR_HEIGHT bitsPerSample: 8 samplesPerPixel: 4
hasAlpha: YES isPlanar: NO colorSpaceName:
NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
NSImage * img = [[NSImage alloc] initWithSize: [fBitmap size]];
[img addRepresentation: fBitmap];
[img setFlipped: YES];
[self buildBar];
[img drawAtPoint: pen fromRect: NSMakeRect( 0, 0,
[img size].width, [img size].height )
operation: NSCompositeSourceOver fraction: 1.0];
[img release];
[fBitmap release];
/* Status strings */
[attributes setObject: [NSFont messageFontOfSize: 9]
forKey: NSFontAttributeName];
pen.y += BAR_HEIGHT + 2;
[[fTorrent statusString] drawAtPoint: pen withAttributes: attributes];
pen.y += 13;
string = [[fTorrent infoString] stringFittingInWidth:
( cellFrame.size.width - 77 ) withAttributes: attributes];
[string drawAtPoint: pen withAttributes: attributes];
/* Rates */
pen.x += fWidth - 70; pen.y -= 13;
[[fTorrent downloadString] drawAtPoint: pen withAttributes: attributes];
pen.y += 13;
[[fTorrent uploadString] drawAtPoint: pen withAttributes: attributes];
[view unlockFocus];
}
@end

View File

@@ -6,15 +6,14 @@
@interface TorrentTableView : NSTableView
{
IBOutlet Controller * fController;
IBOutlet Controller * fController;
NSArray * fTorrents;
tr_stat_t * fStat;
NSPoint fClickPoint;
NSPoint fClickPoint;
IBOutlet NSMenu * fContextRow;
IBOutlet NSMenu * fContextNoRow;
}
- (void) updateUI: (tr_stat_t *) stat;
- (void) setTorrents: (NSArray *) torrents;
@end

View File

@@ -1,31 +1,49 @@
/******************************************************************************
* Copyright (c) 2005-2006 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#import "TorrentTableView.h"
#import "Controller.h"
#import "Torrent.h"
@implementation TorrentTableView
- (void) updateUI: (tr_stat_t *) stat
- (void) setTorrents: (NSArray *) torrents
{
fStat = stat;
[self reloadData];
fTorrents = torrents;
}
- (void) pauseOrResume: (int) row
{
if( fStat[row].status & TR_STATUS_PAUSE )
Torrent * torrent = [fTorrents objectAtIndex: row];
if( [torrent isPaused] )
{
[fController resumeTorrentWithIndex: row];
}
else if( fStat[row].status & ( TR_STATUS_CHECK |
TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
else if( [torrent isActive] )
{
[fController stopTorrentWithIndex: row];
}
}
- (void) mouseDown: (NSEvent *) e
{
fClickPoint = [self convertPoint: [e locationInWindow] fromView: NULL];
[self display];
}
else;
}
- (NSRect) pauseRectForRow: (int) row
@@ -33,11 +51,10 @@
int col;
NSRect cellRect, rect;
col = [self columnWithIdentifier: @"Name"];
col = [self columnWithIdentifier: @"Torrent"];
cellRect = [self frameOfCellAtColumn: col row: row];
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 19,
cellRect.origin.y + cellRect.size.height - 38,
14, 14 );
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 39,
cellRect.origin.y + 3, 14, 14 );
return rect;
}
@@ -47,11 +64,10 @@
int col;
NSRect cellRect, rect;
col = [self columnWithIdentifier: @"Name"];
col = [self columnWithIdentifier: @"Torrent"];
cellRect = [self frameOfCellAtColumn: col row: row];
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 19,
cellRect.origin.y + cellRect.size.height - 19,
14, 14 );
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 20,
cellRect.origin.y + 3, 14, 14 );
return rect;
}
@@ -68,41 +84,60 @@
[self rowAtPoint: point]] );
}
- (void) mouseDown: (NSEvent *) e
{
fClickPoint = [self convertPoint: [e locationInWindow] fromView: nil];
int row = [self rowAtPoint: fClickPoint];
if( [e modifierFlags] & NSAlternateKeyMask )
{
[fController advancedChanged: self];
fClickPoint = NSMakePoint( 0, 0 );
}
else if( ![self pointInPauseRect: fClickPoint] &&
![self pointInRevealRect: fClickPoint] )
{
if( row >= 0 )
{
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
byExtendingSelection: NO];
}
else
{
[self deselectAll: self];
}
}
else;
[self display];
}
- (void) mouseUp: (NSEvent *) e
{
NSPoint point;
int row, col;
int row;
bool sameRow;
Torrent * torrent;
point = [self convertPoint: [e locationInWindow] fromView: NULL];
point = [self convertPoint: [e locationInWindow] fromView: nil];
row = [self rowAtPoint: point];
col = [self columnAtPoint: point];
if( row < 0 )
{
[self deselectAll: NULL];
}
else if( [self pointInPauseRect: point] )
sameRow = row == [self rowAtPoint: fClickPoint];
if( sameRow && [self pointInPauseRect: point]
&& [self pointInPauseRect: fClickPoint] )
{
[self pauseOrResume: row];
}
else if( [self pointInRevealRect: point] )
else if( sameRow && [self pointInRevealRect: point]
&& [self pointInRevealRect: fClickPoint] )
{
[fController finderReveal: [NSString stringWithFormat:
@"%@/%@", [NSString stringWithUTF8String: fStat[row].folder],
[NSString stringWithUTF8String: fStat[row].info.name]]];
[self display];
}
else if( row >= 0 && col == [self columnWithIdentifier: @"Progress"]
&& ( [e modifierFlags] & NSAlternateKeyMask ) )
{
[fController advancedChanged: NULL];
}
else
{
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
byExtendingSelection: NO];
torrent = [fTorrents objectAtIndex: row];
[torrent reveal];
}
else;
[self display];
fClickPoint = NSMakePoint( 0, 0 );
}
@@ -111,52 +146,67 @@
NSPoint point;
int row;
point = [self convertPoint: [e locationInWindow] fromView: NULL];
point = [self convertPoint: [e locationInWindow] fromView: nil];
row = [self rowAtPoint: point];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
return row >= 0 ? fContextRow : fContextNoRow;
if( row >= 0 )
{
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
byExtendingSelection: NO];
return fContextRow;
}
else
{
[self deselectAll: self];
return fContextNoRow;
}
}
- (void) drawRect: (NSRect) r
{
int i;
unsigned i;
NSRect rect;
NSPoint point;
NSImage * image;
Torrent * torrent;
[super drawRect: r];
for( i = 0; i < [self numberOfRows]; i++ )
for( i = 0; i < [fTorrents count]; i++ )
{
torrent = [fTorrents objectAtIndex: i];
rect = [self pauseRectForRow: i];
image = NULL;
if( fStat[i].status & TR_STATUS_PAUSE )
image = nil;
if( [torrent isPaused] )
{
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"ResumeOn.png"] :
[NSImage imageNamed: @"ResumeOff.png"];
}
else if( fStat[i].status &
( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
else if( [torrent isActive] )
{
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"PauseOn.png"] :
[NSImage imageNamed: @"PauseOff.png"];
}
else;
if( image )
{
point = NSMakePoint( rect.origin.x, rect.origin.y + 14 );
[image compositeToPoint: point operation: NSCompositeSourceOver];
[image setFlipped: YES];
[image drawAtPoint: rect.origin fromRect:
NSMakeRect( 0, 0, 14, 14 ) operation:
NSCompositeSourceOver fraction: 1.0];
}
rect = [self revealRectForRow: i];
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"RevealOn.png"] :
[NSImage imageNamed: @"RevealOff.png"];
point = NSMakePoint( rect.origin.x, rect.origin.y + 14 );
[image compositeToPoint: point operation: NSCompositeSourceOver];
[image setFlipped: YES];
[image drawAtPoint: rect.origin fromRect:
NSMakeRect( 0, 0, 14, 14 ) operation:
NSCompositeSourceOver fraction: 1.0];
}
}

View File

@@ -8,8 +8,6 @@
/* Begin PBXBuildFile section */
4D043A7F090AE979009FEDA8 /* TransmissionDocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */; };
4D096C12089FB4E20091B166 /* NameCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D096C0F089FB4E20091B166 /* NameCell.m */; };
4D096C13089FB4E20091B166 /* ProgressCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D096C11089FB4E20091B166 /* ProgressCell.m */; };
4D118E1A08CB46B20033958F /* PrefsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D118E1908CB46B20033958F /* PrefsController.m */; };
4D2784370905709500687951 /* Transmission.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D2784360905709500687951 /* Transmission.icns */; };
4D364DA0091FBB2C00377D12 /* TorrentTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D364D9F091FBB2C00377D12 /* TorrentTableView.m */; };
@@ -17,12 +15,12 @@
4D6DAAC6090CE00500F43C22 /* RevealOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC4090CE00500F43C22 /* RevealOff.png */; };
4D6DAAC7090CE00500F43C22 /* RevealOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC5090CE00500F43C22 /* RevealOn.png */; };
4D752E930913C949008EAAD4 /* Preferences.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D752E920913C949008EAAD4 /* Preferences.png */; };
4D813EB508AA43AC00191DB4 /* Progress.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D813EB408AA43AC00191DB4 /* Progress.png */; };
4D8CEF91095870E00063BAEA /* Network.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D8CEF90095870E00063BAEA /* Network.png */; };
4DA6FDBA0911233800450CB1 /* PauseOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDB80911233800450CB1 /* PauseOn.png */; };
4DA6FDBB0911233800450CB1 /* PauseOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDB90911233800450CB1 /* PauseOff.png */; };
4DA6FDC5091141AD00450CB1 /* ResumeOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDC3091141AD00450CB1 /* ResumeOff.png */; };
4DA6FDC6091141AD00450CB1 /* ResumeOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DA6FDC4091141AD00450CB1 /* ResumeOn.png */; };
4DCCBB3E09C3D71100D3CABF /* TorrentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DCCBB3C09C3D71100D3CABF /* TorrentCell.m */; };
4DDFDD22099A5D8E00189D81 /* DownloadBadge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DDFDD20099A5D8E00189D81 /* DownloadBadge.png */; };
4DDFDD23099A5D8E00189D81 /* UploadBadge.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DDFDD21099A5D8E00189D81 /* UploadBadge.png */; };
4DE5CC9D0980656F00BE280E /* StringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DE5CC9C0980656F00BE280E /* StringAdditions.m */; };
@@ -36,6 +34,7 @@
4DF7500C08A103AD007B0D70 /* Open.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500708A103AD007B0D70 /* Open.png */; };
4DF7500D08A103AD007B0D70 /* Info.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500808A103AD007B0D70 /* Info.png */; };
4DF7500E08A103AD007B0D70 /* Remove.png in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500908A103AD007B0D70 /* Remove.png */; };
4DFBC2DF09C0970D00D5C571 /* Torrent.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DFBC2DE09C0970D00D5C571 /* Torrent.m */; };
8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; };
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
@@ -79,10 +78,6 @@
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transmission_Prefix.pch; sourceTree = "<group>"; };
4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = TransmissionDocument.icns; path = Images/TransmissionDocument.icns; sourceTree = "<group>"; };
4D096C0E089FB4E20091B166 /* NameCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NameCell.h; sourceTree = "<group>"; };
4D096C0F089FB4E20091B166 /* NameCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NameCell.m; sourceTree = "<group>"; };
4D096C10089FB4E20091B166 /* ProgressCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ProgressCell.h; sourceTree = "<group>"; };
4D096C11089FB4E20091B166 /* ProgressCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ProgressCell.m; sourceTree = "<group>"; };
4D118E1808CB46B20033958F /* PrefsController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PrefsController.h; sourceTree = "<group>"; };
4D118E1908CB46B20033958F /* PrefsController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PrefsController.m; sourceTree = "<group>"; };
4D2784360905709500687951 /* Transmission.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Transmission.icns; path = Images/Transmission.icns; sourceTree = "<group>"; };
@@ -92,12 +87,13 @@
4D6DAAC4090CE00500F43C22 /* RevealOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOff.png; path = Images/RevealOff.png; sourceTree = "<group>"; };
4D6DAAC5090CE00500F43C22 /* RevealOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOn.png; path = Images/RevealOn.png; sourceTree = "<group>"; };
4D752E920913C949008EAAD4 /* Preferences.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Preferences.png; path = Images/Preferences.png; sourceTree = "<group>"; };
4D813EB408AA43AC00191DB4 /* Progress.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Progress.png; path = Images/Progress.png; sourceTree = "<group>"; };
4D8CEF90095870E00063BAEA /* Network.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Network.png; path = Images/Network.png; sourceTree = "<group>"; };
4DA6FDB80911233800450CB1 /* PauseOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = PauseOn.png; path = Images/PauseOn.png; sourceTree = "<group>"; };
4DA6FDB90911233800450CB1 /* PauseOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = PauseOff.png; path = Images/PauseOff.png; sourceTree = "<group>"; };
4DA6FDC3091141AD00450CB1 /* ResumeOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeOff.png; path = Images/ResumeOff.png; sourceTree = "<group>"; };
4DA6FDC4091141AD00450CB1 /* ResumeOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = ResumeOn.png; path = Images/ResumeOn.png; sourceTree = "<group>"; };
4DCCBB3C09C3D71100D3CABF /* TorrentCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = TorrentCell.m; sourceTree = "<group>"; };
4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TorrentCell.h; sourceTree = "<group>"; };
4DDFDD20099A5D8E00189D81 /* DownloadBadge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = DownloadBadge.png; path = Images/DownloadBadge.png; sourceTree = "<group>"; };
4DDFDD21099A5D8E00189D81 /* UploadBadge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = UploadBadge.png; path = Images/UploadBadge.png; sourceTree = "<group>"; };
4DE5CC9B0980656F00BE280E /* StringAdditions.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StringAdditions.h; sourceTree = "<group>"; };
@@ -114,6 +110,8 @@
4DF7500708A103AD007B0D70 /* Open.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Open.png; path = Images/Open.png; sourceTree = "<group>"; };
4DF7500808A103AD007B0D70 /* Info.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Info.png; path = Images/Info.png; sourceTree = "<group>"; };
4DF7500908A103AD007B0D70 /* Remove.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Remove.png; path = Images/Remove.png; sourceTree = "<group>"; };
4DFBC2DD09C0970D00D5C571 /* Torrent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Torrent.h; sourceTree = "<group>"; };
4DFBC2DE09C0970D00D5C571 /* Torrent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Torrent.m; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* Transmission.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transmission.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -135,10 +133,6 @@
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
4D096C0E089FB4E20091B166 /* NameCell.h */,
4D096C0F089FB4E20091B166 /* NameCell.m */,
4D096C10089FB4E20091B166 /* ProgressCell.h */,
4D096C11089FB4E20091B166 /* ProgressCell.m */,
4DF0C5A90899190500DD8943 /* Controller.m */,
4DF0C5AA0899190500DD8943 /* Controller.h */,
4D118E1808CB46B20033958F /* PrefsController.h */,
@@ -149,6 +143,10 @@
4DE5CC9C0980656F00BE280E /* StringAdditions.m */,
4DE5CCA50980735700BE280E /* Badger.h */,
4DE5CCA60980735700BE280E /* Badger.m */,
4DFBC2DD09C0970D00D5C571 /* Torrent.h */,
4DFBC2DE09C0970D00D5C571 /* Torrent.m */,
4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */,
4DCCBB3C09C3D71100D3CABF /* TorrentCell.m */,
);
name = Classes;
sourceTree = "<group>";
@@ -208,7 +206,6 @@
4D2784360905709500687951 /* Transmission.icns */,
4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */,
4DF7500808A103AD007B0D70 /* Info.png */,
4D813EB408AA43AC00191DB4 /* Progress.png */,
4DF7500708A103AD007B0D70 /* Open.png */,
4DF7500908A103AD007B0D70 /* Remove.png */,
4D6DAAC4090CE00500F43C22 /* RevealOff.png */,
@@ -304,7 +301,6 @@
4DF7500C08A103AD007B0D70 /* Open.png in Resources */,
4DF7500D08A103AD007B0D70 /* Info.png in Resources */,
4DF7500E08A103AD007B0D70 /* Remove.png in Resources */,
4D813EB508AA43AC00191DB4 /* Progress.png in Resources */,
4D2784370905709500687951 /* Transmission.icns in Resources */,
4D043A7F090AE979009FEDA8 /* TransmissionDocument.icns in Resources */,
4D6DAAC6090CE00500F43C22 /* RevealOff.png in Resources */,
@@ -333,12 +329,12 @@
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
4DF0C5AB0899190500DD8943 /* Controller.m in Sources */,
4D096C12089FB4E20091B166 /* NameCell.m in Sources */,
4D096C13089FB4E20091B166 /* ProgressCell.m in Sources */,
4D118E1A08CB46B20033958F /* PrefsController.m in Sources */,
4D364DA0091FBB2C00377D12 /* TorrentTableView.m in Sources */,
4DE5CC9D0980656F00BE280E /* StringAdditions.m in Sources */,
4DE5CCA70980735700BE280E /* Badger.m in Sources */,
4DFBC2DF09C0970D00D5C571 /* Torrent.m in Sources */,
4DCCBB3E09C3D71100D3CABF /* TorrentCell.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -59,9 +59,10 @@ static void sigHandler ( int signal );
int main( int argc, char ** argv )
{
int i, count;
tr_handle_t * h;
tr_stat_t * s;
int i, error;
tr_handle_t * h;
tr_torrent_t * tor;
tr_stat_t * s;
printf( "Transmission %s - http://transmission.m0k.org/\n\n",
VERSION_STRING );
@@ -104,7 +105,7 @@ int main( int argc, char ** argv )
h = tr_init();
/* Open and parse torrent file */
if( tr_torrentInit( h, torrentPath ) )
if( !( tor = tr_torrentInit( h, torrentPath, &error ) ) )
{
printf( "Failed opening torrent file `%s'\n", torrentPath );
goto failed;
@@ -112,10 +113,7 @@ int main( int argc, char ** argv )
if( showInfo )
{
tr_info_t * info;
count = tr_torrentStat( h, &s );
info = &s[0].info;
tr_info_t * info = tr_torrentInfo( tor );
/* Print torrent info (quite <20> la btshowmetainfo) */
printf( "hash: " );
@@ -137,7 +135,6 @@ int main( int argc, char ** argv )
info->files[i].length );
}
free( s );
goto cleanup;
}
@@ -145,7 +142,7 @@ int main( int argc, char ** argv )
{
int seeders, leechers;
if( tr_torrentScrape( h, 0, &seeders, &leechers ) )
if( tr_torrentScrape( tor, &seeders, &leechers ) )
{
printf( "Scrape failed.\n" );
}
@@ -162,8 +159,8 @@ int main( int argc, char ** argv )
tr_setBindPort( h, bindPort );
tr_setUploadLimit( h, uploadLimit );
tr_torrentSetFolder( h, 0, "." );
tr_torrentStart( h, 0 );
tr_torrentSetFolder( tor, "." );
tr_torrentStart( tor );
while( !mustDie )
{
@@ -173,69 +170,64 @@ int main( int argc, char ** argv )
sleep( 1 );
count = tr_torrentStat( h, &s );
s = tr_torrentStat( tor );
if( s[0].status & TR_STATUS_CHECK )
if( s->status & TR_STATUS_CHECK )
{
chars = snprintf( string, 80,
"Checking files... %.2f %%", 100.0 * s[0].progress );
"Checking files... %.2f %%", 100.0 * s->progress );
}
else if( s[0].status & TR_STATUS_DOWNLOAD )
else if( s->status & TR_STATUS_DOWNLOAD )
{
chars = snprintf( string, 80,
"Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), "
"ul to %d (%.2f KB/s)", 100.0 * s[0].progress,
s[0].peersTotal, ( s[0].peersTotal == 1 ) ? "" : "s",
s[0].peersUploading, s[0].rateDownload,
s[0].peersDownloading, s[0].rateUpload );
"ul to %d (%.2f KB/s)", 100.0 * s->progress,
s->peersTotal, ( s->peersTotal == 1 ) ? "" : "s",
s->peersUploading, s->rateDownload,
s->peersDownloading, s->rateUpload );
}
else if( s[0].status & TR_STATUS_SEED )
else if( s->status & TR_STATUS_SEED )
{
chars = snprintf( string, 80,
"Seeding, uploading to %d of %d peer(s), %.2f KB/s",
s[0].peersDownloading, s[0].peersTotal,
s[0].rateUpload );
s->peersDownloading, s->peersTotal,
s->rateUpload );
}
memset( &string[chars], ' ', 79 - chars );
string[79] = '\0';
fprintf( stderr, "\r%s", string );
if( s[0].status & TR_TRACKER_ERROR )
if( s->error & TR_ETRACKER )
{
fprintf( stderr, "\n%s\n", s[0].error );
fprintf( stderr, "\n%s\n", s->trackerError );
}
else if( verboseLevel > 0 )
{
fprintf( stderr, "\n" );
}
if( tr_getFinished( h, 0 ) )
if( tr_getFinished( tor ) )
{
tr_setFinished( h, 0, 0 );
result = system(finishCall);
}
free( s );
}
fprintf( stderr, "\n" );
/* Try for 5 seconds to notice the tracker that we are leaving */
tr_torrentStop( h, 0 );
tr_torrentStop( tor );
for( i = 0; i < 10; i++ )
{
count = tr_torrentStat( h, &s );
if( s[0].status & TR_STATUS_PAUSE )
s = tr_torrentStat( tor );
if( s->status & TR_STATUS_PAUSE )
{
/* The 'stopped' message was sent */
free( s );
break;
}
free( s );
usleep( 500000 );
}
cleanup:
tr_torrentClose( h, 0 );
tr_torrentClose( h, tor );
failed:
tr_close( h );