#838 (Adding trackers to existing torrents): add a backend API for this (tr_torrentSetAnnounceList) and implement a GUI for it in the gtk+ client.

This commit is contained in:
Charles Kerr 2008-06-01 01:40:32 +00:00
parent 0a023367b6
commit 07c1b28e9e
7 changed files with 416 additions and 25 deletions

View File

@ -10,6 +10,7 @@
* $Id$
*/
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@ -17,6 +18,7 @@
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> /* tr_httpIsValidURL */
#include "actions.h"
#include "details.h"
@ -1078,6 +1080,15 @@ struct tracker_page
{
TrTorrent * gtor;
GtkTreeView * view;
GtkListStore * store;
GtkTreeSelection * sel;
GtkWidget * add_button;
GtkWidget * remove_button;
GtkWidget * save_button;
GtkWidget * revert_button;
GtkWidget * last_scrape_time_lb;
GtkWidget * last_scrape_response_lb;
GtkWidget * next_scrape_countdown_lb;
@ -1088,19 +1099,291 @@ struct tracker_page
GtkWidget * manual_announce_countdown_lb;
};
enum
{
TR_COL_TIER,
TR_COL_ANNOUNCE,
TR_N_COLS
};
static void
setTrackerChangeState( struct tracker_page * page, gboolean changed )
{
gtk_widget_set_sensitive( page->save_button, changed );
gtk_widget_set_sensitive( page->revert_button, changed );
}
static GtkTreeModel*
tracker_model_new( tr_torrent * tor )
{
int i;
const tr_info * inf = tr_torrentInfo( tor );
GtkListStore * store = gtk_list_store_new( TR_N_COLS, G_TYPE_INT, G_TYPE_STRING );
for( i=0; i<inf->trackerCount; ++i )
{
GtkTreeIter iter;
const tr_tracker_info * tinf = inf->trackers + i;
gtk_list_store_append( store, &iter );
gtk_list_store_set( store, &iter, TR_COL_TIER, tinf->tier + 1,
TR_COL_ANNOUNCE, tinf->announce,
-1 );
}
gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
TR_COL_TIER,
GTK_SORT_ASCENDING );
return GTK_TREE_MODEL( store );
}
static void
onTrackerSelectionChanged( GtkTreeSelection * sel,
gpointer gpage )
{
struct tracker_page * page = gpage;
gboolean has_selection = gtk_tree_selection_get_selected( sel, NULL, NULL );
gtk_widget_set_sensitive( page->remove_button, has_selection );
}
static void
onTrackerRemoveClicked( GtkButton * w UNUSED, gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeIter iter;
if( gtk_tree_selection_get_selected( page->sel, NULL, &iter ) ) {
gtk_list_store_remove( page->store, &iter );
setTrackerChangeState( page, TRUE );
}
}
static void
onTrackerAddClicked( GtkButton * w UNUSED, gpointer gpage )
{
GtkTreeIter iter;
struct tracker_page * page = gpage;
GtkTreePath * path;
gtk_list_store_append( page->store, &iter );
setTrackerChangeState( page, TRUE );
gtk_list_store_set( page->store, &iter, TR_COL_TIER, 1,
TR_COL_ANNOUNCE, _( "Announce URL" ),
-1 );
path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
gtk_tree_view_set_cursor( page->view,
path,
gtk_tree_view_get_column( page->view, TR_COL_ANNOUNCE ),
TRUE );
gtk_tree_path_free( path );
}
static void
onTrackerSaveClicked( GtkButton * w UNUSED, gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
const int n = gtk_tree_model_iter_n_children( model, NULL );
if( n > 0 ) /* must have at least one tracker */
{
int i = 0;
GtkTreeIter iter;
tr_tracker_info * trackers;
/* build the tracker list */
trackers = g_new0( tr_tracker_info, n );
if( gtk_tree_model_get_iter_first( model, &iter ) ) do {
gtk_tree_model_get( model, &iter, TR_COL_TIER, &trackers[i].tier,
TR_COL_ANNOUNCE, &trackers[i].announce,
-1 );
++i;
} while( gtk_tree_model_iter_next( model, &iter ) );
g_assert( i == n );
/* set the tracker list */
tr_torrentSetAnnounceList( tr_torrent_handle( page->gtor ),
trackers, n );
setTrackerChangeState( page, FALSE );
/* cleanup */
for( i=0; i<n; ++i )
g_free( trackers[i].announce );
g_free( trackers );
}
}
static void
onTrackerRevertClicked( GtkButton * w UNUSED, gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = tracker_model_new( tr_torrent_handle( page->gtor ) );
gtk_tree_view_set_model( page->view, model );
page->store = GTK_LIST_STORE( model );
g_object_unref( G_OBJECT( model ) );
setTrackerChangeState( page, FALSE );
}
static void
onAnnounceEdited( GtkCellRendererText * renderer UNUSED,
gchar * path_string,
gchar * new_text,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
GtkTreeIter iter;
GtkTreePath * path = gtk_tree_path_new_from_string( path_string ) ;
if( gtk_tree_model_get_iter( model, &iter, path ) )
{
char * old_text;
gtk_tree_model_get( model, &iter, TR_COL_ANNOUNCE, &old_text, -1 );
if( tr_httpIsValidURL( new_text ) )
{
if( strcmp( old_text, new_text ) )
{
gtk_list_store_set( page->store, &iter, TR_COL_ANNOUNCE, new_text, -1 );
setTrackerChangeState( page, TRUE );
}
}
else if( !tr_httpIsValidURL( old_text ) )
{
/* both old and new are invalid...
they must've typed in an invalid URL
after hitting the "Add" button */
onTrackerRemoveClicked( NULL, page );
setTrackerChangeState( page, TRUE );
}
g_free( old_text );
}
gtk_tree_path_free( path );
}
static void
onTierEdited( GtkCellRendererText * renderer UNUSED,
gchar * path_string,
gchar * new_text,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
GtkTreeIter iter;
GtkTreePath * path;
char * end;
int new_tier;
errno = 0;
new_tier = strtol( new_text, &end, 10 );
if( new_tier<1 || *end || errno )
return;
path = gtk_tree_path_new_from_string( path_string ) ;
if( gtk_tree_model_get_iter( model, &iter, path ) )
{
int old_tier;
gtk_tree_model_get( model, &iter, TR_COL_TIER, &old_tier, -1 );
if( old_tier != new_tier )
{
gtk_list_store_set( page->store, &iter, TR_COL_TIER, new_tier, -1 );
setTrackerChangeState( page, TRUE );
}
}
gtk_tree_path_free( path );
}
GtkWidget*
tracker_page_new( TrTorrent * gtor )
{
GtkWidget * t;
GtkWidget * l;
GtkWidget * w;
GtkWidget * h;
GtkWidget * v;
GtkWidget * fr;
int row = 0;
const char * s;
GtkTreeModel * m;
GtkCellRenderer * r;
GtkTreeViewColumn * c;
GtkTreeSelection * sel;
struct tracker_page * page = g_new0( struct tracker_page, 1 );
const tr_info * info = tr_torrent_info (gtor);
page->gtor = gtor;
t = hig_workarea_create( );
hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
h = gtk_hbox_new( FALSE, GUI_PAD );
m = tracker_model_new( tr_torrent_handle( gtor ) );
page->store = GTK_LIST_STORE( m );
w = gtk_tree_view_new_with_model( m );
page->view = GTK_TREE_VIEW( w );
gtk_tree_view_set_enable_search( page->view, FALSE );
r = gtk_cell_renderer_text_new( );
g_object_set( G_OBJECT( r ),
"editable", TRUE,
NULL );
g_signal_connect( r, "edited",
G_CALLBACK( onTierEdited ), page );
c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r,
"text", TR_COL_TIER,
NULL );
gtk_tree_view_column_set_sort_column_id( c, TR_COL_TIER );
gtk_tree_view_append_column( page->view, c );
r = gtk_cell_renderer_text_new( );
g_object_set( G_OBJECT( r ),
"editable", TRUE,
"ellipsize", PANGO_ELLIPSIZE_END,
NULL );
g_signal_connect( r, "edited",
G_CALLBACK( onAnnounceEdited ), page );
c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r,
"text", TR_COL_ANNOUNCE,
NULL );
gtk_tree_view_column_set_sort_column_id( c, TR_COL_ANNOUNCE );
gtk_tree_view_append_column( page->view, c );
w = gtk_scrolled_window_new( NULL, NULL );
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC );
sel = gtk_tree_view_get_selection( page->view );
page->sel = sel;
g_signal_connect( sel, "changed",
G_CALLBACK( onTrackerSelectionChanged ), page );
gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( page->view ) );
gtk_widget_set_size_request( w, -1, 133 );
fr = gtk_frame_new( NULL );
gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
gtk_container_add( GTK_CONTAINER( fr ), w );
gtk_box_pack_start_defaults( GTK_BOX( h ), fr );
g_object_unref( G_OBJECT( m ) );
v = gtk_vbox_new( TRUE, GUI_PAD_SMALL );
w = gtk_button_new_from_stock( GTK_STOCK_ADD );
g_signal_connect( w, "clicked", G_CALLBACK( onTrackerAddClicked ), page );
gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
page->add_button = w;
w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
g_signal_connect( w, "clicked", G_CALLBACK( onTrackerRemoveClicked ), page );
gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
page->remove_button = w;
w = gtk_button_new_from_stock( GTK_STOCK_SAVE );
g_signal_connect( w, "clicked", G_CALLBACK( onTrackerSaveClicked ), page );
gtk_widget_set_sensitive( w, FALSE );
gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
page->save_button = w;
w = gtk_button_new_from_stock( GTK_STOCK_REVERT_TO_SAVED );
g_signal_connect( w, "clicked", G_CALLBACK( onTrackerRevertClicked ), page );
gtk_widget_set_sensitive( w, FALSE );
gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
page->revert_button = w;
gtk_box_pack_start( GTK_BOX( h ), v, FALSE, FALSE, 0 );
hig_workarea_add_wide_control( t, &row, h );
onTrackerSelectionChanged( sel, page );
hig_workarea_add_section_divider( t, &row );
hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
s = _( "Last scrape at:" );
@ -1147,8 +1430,8 @@ tracker_page_new( TrTorrent * gtor )
page->manual_announce_countdown_lb = l;
hig_workarea_add_row( t, &row, s, l, NULL );
g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
hig_workarea_finish( t, &row );
g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
return t;
}

View File

@ -634,7 +634,7 @@ file_list_new( TrTorrent * gtor )
/* add priority column */
rend = gtk_cell_renderer_text_new( );
col = gtk_tree_view_column_new_with_attributes( _( "Priorities" ), rend, NULL );
col = gtk_tree_view_column_new_with_attributes( _( "Priority" ), rend, NULL );
gtk_tree_view_column_set_cell_data_func( col, rend, renderPriority, NULL, NULL);
gtk_tree_view_append_column ( GTK_TREE_VIEW( view ), col);
@ -645,7 +645,7 @@ file_list_new( TrTorrent * gtor )
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
GTK_SHADOW_IN);
gtk_container_add( GTK_CONTAINER( scroll ), view );
gtk_widget_set_size_request (scroll, 0u, 200u);
gtk_widget_set_size_request (scroll, -1, 200 );
data = g_new0( FileData, 1 );

View File

@ -699,7 +699,7 @@ wannaquit( void * vdata )
gtk_table_attach_defaults( GTK_TABLE( p ), w, 1, 2, 1, 2 );
b = gtk_alignment_new(0.0, 1.0, 0.01, 0.01);
w = gtk_button_new_with_label( _( "_Quit Immediately" ) );
w = gtk_button_new_with_label( _( "_Quit Now" ) );
gtk_button_set_image( GTK_BUTTON(w), gtk_image_new_from_stock( GTK_STOCK_QUIT, GTK_ICON_SIZE_BUTTON ) );
g_signal_connect(w, "clicked", G_CALLBACK(do_exit_cb), NULL);
gtk_container_add(GTK_CONTAINER(b), w);

View File

@ -37,6 +37,8 @@
#include "ptrarray.h"
#include "utils.h" /* tr_new(), tr_free() */
const tr_benc BENC_NULL = { 0, { 0 } };
/**
***
**/
@ -328,13 +330,13 @@ tr_bencLoad( const void * buf_in,
****
***/
tr_benc *
tr_bencDictFind( tr_benc * val, const char * key )
static int
dictIndexOf( tr_benc * val, const char * key )
{
int len, ii;
if( !tr_bencIsDict( val ) )
return NULL;
return -1;
len = strlen( key );
@ -346,10 +348,17 @@ tr_bencDictFind( tr_benc * val, const char * key )
{
continue;
}
return &val->val.l.vals[ii+1];
return ii;
}
return NULL;
return -1;
}
tr_benc *
tr_bencDictFind( tr_benc * val, const char * key )
{
const int i = dictIndexOf( val, key );
return i<0 ? NULL : &val->val.l.vals[i+1];
}
tr_benc*
@ -646,6 +655,30 @@ tr_bencDictAddRaw( tr_benc * dict, const char * key, const void * src, size_t le
return child;
}
int
tr_bencDictRemove( tr_benc * dict, const char * key )
{
int i = dictIndexOf( dict, key );
if( i >= 0 )
{
const int n = dict->val.l.count;
fprintf( stderr, "i is %d... count is %d\n", i, dict->val.l.count );
fprintf( stderr, "moving %d items from pos %d to %d\n", dict->val.l.count-(i+2), i+2, i );
#if 0
tr_bencFree( &dict->val.l.vals[i] );
tr_bencFree( &dict->val.l.vals[i+1] );
#endif
if( i + 2 < n )
{
dict->val.l.vals[i] = dict->val.l.vals[n-2];
dict->val.l.vals[i+1] = dict->val.l.vals[n-1];
}
dict->val.l.count -= 2;
}
return i >= 0; /* return true if found */
}
/***
**** BENC WALKING
***/
@ -909,7 +942,7 @@ freeContainerBeginFunc( const tr_benc * val, void * freeme )
void
tr_bencFree( tr_benc * val )
{
if( val != NULL )
if( val && val->type )
{
tr_ptrArray * freeme = tr_ptrArrayNew( );
struct WalkFuncs walkFuncs;

View File

@ -53,6 +53,8 @@ typedef struct tr_benc
} val;
} tr_benc;
const tr_benc BENC_INIT;
/* backwards compatability */
typedef tr_benc benc_val_t;
@ -108,6 +110,7 @@ tr_benc * tr_bencDictAddStr( tr_benc * dict, const char * key, const char * v
tr_benc * tr_bencDictAddList( tr_benc * dict, const char * key, int reserveCount );
tr_benc * tr_bencDictAddDict( tr_benc * dict, const char * key, int reserveCount );
tr_benc * tr_bencDictAddRaw( tr_benc * dict, const char * key, const void *, size_t len );
int tr_bencDictRemove( tr_benc * dict, const char * key );
char* tr_bencSave( const tr_benc * val, int * len );
char* tr_bencSaveAsJSON( const tr_benc * top, int * len );

View File

@ -307,17 +307,18 @@ tr_torrentInitFilePieces( tr_torrent * tor )
tr_file_index_t ff;
tr_piece_index_t pp;
uint64_t offset = 0;
tr_info * inf = &tor->info;
assert( tor != NULL );
assert( inf != NULL );
for( ff=0; ff<tor->info.fileCount; ++ff ) {
tor->info.files[ff].offset = offset;
offset += tor->info.files[ff].length;
initFilePieces( &tor->info, ff );
for( ff=0; ff<inf->fileCount; ++ff ) {
inf->files[ff].offset = offset;
offset += inf->files[ff].length;
initFilePieces( inf, ff );
}
for( pp=0; pp<tor->info.pieceCount; ++pp )
tor->info.pieces[pp].priority = calculatePiecePriority( tor, pp, -1 );
for( pp=0; pp<inf->pieceCount; ++pp )
inf->pieces[pp].priority = calculatePiecePriority( tor, pp, -1 );
}
int
@ -1517,3 +1518,54 @@ tr_torrentGetMTimes( const tr_torrent * tor, int * setme_n )
*setme_n = n;
return m;
}
/***
****
***/
void
tr_torrentSetAnnounceList( tr_torrent * tor,
const tr_tracker_info * trackers,
int trackerCount )
{
tr_benc metainfo = BENC_INIT;
/* save to the .torrent file */
if( !tr_bencLoadFile( tor->info.torrent, &metainfo ) )
{
int i;
int prevTier = -1;
tr_benc * tier = NULL;
tr_benc * announceList;
tr_info tmpInfo;
/* remove the old fields */
tr_bencDictRemove( &metainfo, "announce" );
tr_bencDictRemove( &metainfo, "announce-list" );
/* add the new fields */
tr_bencDictAddStr( &metainfo, "announce", trackers[0].announce );
announceList = tr_bencDictAddList( &metainfo, "announce-list", 0 );
for( i=0; i<trackerCount; ++i ) {
if( prevTier != trackers[i].tier ) {
prevTier = trackers[i].tier;
tier = tr_bencListAddList( announceList, 0 );
}
tr_bencListAddStr( tier, trackers[i].announce );
}
/* try to parse it back again, to make sure it's good */
memset( &tmpInfo, 0, sizeof( tr_info ) );
if( !tr_metainfoParse( tor->handle, &tmpInfo, &metainfo ) )
{
/* if it's good, save it and use it */
tr_metainfoFree( &tor->info );
tor->info = tmpInfo;
tr_torrentInitFilePieces( tor );
tr_bencSaveFile( tor->info.torrent, &metainfo );
}
/* cleanup */
tr_bencFree( &metainfo );
}
}

View File

@ -802,6 +802,34 @@ void tr_torrentSetDownloadDir( tr_torrent *, const char * );
const char * tr_torrentGetDownloadDir( const tr_torrent * );
/**
***
**/
typedef struct tr_tracker_info
{
int tier;
char * announce;
char * scrape;
}
tr_tracker_info;
/**
* @brief Modify a torrent's tracker list.
*
* This updates the torrent in-memory and also the metainfo file
* stored in the torrent folder in tr_sessionGetConfigDir().
*
* @param torrent The torrent whose tracker list is to be modified.
* @param trackers An array of trackers, sorted by tier from first to last.
* NOTE: only the `tier' and `announce' fields are used.
* libtransmission derives `scrape' from `announce'.
* @param trackerCount size of the `trackers' array.
*/
void tr_torrentSetAnnounceList( tr_torrent * torrent,
const tr_tracker_info * trackers,
int trackerCount );
/**
***
@ -937,14 +965,6 @@ typedef struct tr_piece
int8_t dnd; /* nonzero if the piece shouldn't be downloaded */
}
tr_piece;
typedef struct tr_tracker_info
{
int tier;
char * announce;
char * scrape;
}
tr_tracker_info;
struct tr_info
{