{
int err;
tr_metainfo_builder * builder = tr_metaInfoBuilderCreate( h, sourceFile );
- tr_makeMetaInfo( builder, torrentPath, announce, comment, isPrivate );
+ tr_tracker_info ti;
+ ti.tier = 0;
+ ti.announce = announce;
+ tr_makeMetaInfo( builder, torrentPath, &ti, 1, comment, isPrivate );
while( !builder->isDone ) {
tr_wait( 1000 );
printf( "." );
stats.h \
sexy-icon-entry.h \
torrent-cell-renderer.h \
+ tracker-list.h \
tr-core.h \
tr-core-dbus.h \
tr-icon.h \
sexy-icon-entry.c \
stats.c \
torrent-cell-renderer.c \
+ tracker-list.c \
tr-core.c \
tr-icon.c \
tr-io.c \
#include "details.h"
#include "file-list.h"
#include "tr-torrent.h"
+#include "tracker-list.h"
#include "hig.h"
#include "util.h"
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);
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 );
+ w = tracker_list_new( gtor, GTK_POS_RIGHT );
+ hig_workarea_add_wide_control( t, &row, w );
hig_workarea_add_section_divider( t, &row );
hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
#include "hig.h"
#include "makemeta-ui.h"
+#include "tracker-list.h"
#include "util.h"
#define UPDATE_INTERVAL_MSEC 200
{
GtkWidget * size_lb;
GtkWidget * pieces_lb;
- GtkWidget * announce_entry;
+ GtkWidget * announce_list;
GtkWidget * comment_entry;
GtkWidget * progressbar;
GtkWidget * private_check;
char *tmp;
char buf[1024];
guint tag;
+ tr_tracker_info * trackers = NULL;
+ int i;
+ int trackerCount = 0;
if( response != GTK_RESPONSE_ACCEPT )
{
gtk_progress_bar_set_text( GTK_PROGRESS_BAR(ui->progressbar), buf );
g_free( tmp );
+ trackers = tracker_list_get_trackers( ui->announce_list, &trackerCount );
+
tr_makeMetaInfo( ui->builder,
NULL,
- gtk_entry_get_text( GTK_ENTRY( ui->announce_entry ) ),
+ trackers, trackerCount,
gtk_entry_get_text( GTK_ENTRY( ui->comment_entry ) ),
gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( ui->private_check ) ) );
tag = g_timeout_add (UPDATE_INTERVAL_MSEC, refresh_cb, ui);
g_object_set_data_full (G_OBJECT(d), "tag", GUINT_TO_POINTER(tag), remove_tag);
+
+ /* cleanup */
+ for( i=0; i<trackerCount; ++i )
+ g_free( trackers[i].announce );
+ g_free( trackers );
}
/***
hig_workarea_add_section_divider( t, &row );
- hig_workarea_add_section_title (t, &row, _("Details"));
+ hig_workarea_add_section_title (t, &row, _("Tracker"));
w = ui->private_check = hig_workarea_add_wide_checkbutton( t, &row, _( "_Private to this tracker" ), FALSE );
- w = ui->announce_entry = gtk_entry_new( );
- gtk_entry_set_text(GTK_ENTRY(w), "http://");
- hig_workarea_add_row (t, &row, _( "Announce _URL:" ), w, NULL );
+ w = tracker_list_new( NULL, GTK_POS_LEFT );
+ ui->announce_list = w;
+ hig_workarea_add_wide_control (t, &row, w );
+
+ hig_workarea_add_section_divider( t, &row );
+ hig_workarea_add_section_title (t, &row, _("Optional Information"));
w = ui->comment_entry = gtk_entry_new( );
hig_workarea_add_row (t, &row, _( "Commen_t:" ), w, NULL );
}
static int
-isDisposed( const TrTorrent * self )
+isDisposed( const TrTorrent * tor )
{
- return !self || !self->priv;
+ return !tor || !TR_IS_TORRENT( tor ) || !tor->priv;
}
static void
tr_torrent *
tr_torrent_handle(TrTorrent *tor)
{
- g_assert( TR_IS_TORRENT(tor) );
-
return isDisposed( tor ) ? NULL : tor->priv->handle;
}
--- /dev/null
+/*
+ * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
+ *
+ * This file is licensed by the GPL version 2. Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: details.c 5987 2008-06-01 01:40:32Z charles $
+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <libtransmission/transmission.h>
+#include <libtransmission/utils.h> /* tr_httpIsValidURL */
+
+#include "actions.h"
+#include "details.h"
+#include "file-list.h"
+#include "tracker-list.h"
+#include "tr-torrent.h"
+#include "hig.h"
+#include "util.h"
+
+#define UPDATE_INTERVAL_MSEC 2000
+
+
+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;
+
+ GtkWidget * last_announce_time_lb;
+ GtkWidget * last_announce_response_lb;
+ GtkWidget * next_announce_countdown_lb;
+ GtkWidget * manual_announce_countdown_lb;
+};
+
+enum
+{
+ TR_COL_TIER,
+ TR_COL_ANNOUNCE,
+ TR_N_COLS
+};
+
+static void
+setTrackerChangeState( struct tracker_page * page, gboolean changed )
+{
+ if( page->save_button )
+ gtk_widget_set_sensitive( page->save_button, changed );
+
+ if( page->revert_button )
+ 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; inf && 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, _( "http://" ),
+ -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_list_new( TrTorrent * gtor, GtkPositionType buttonPos )
+{
+ GtkWidget * w;
+ GtkWidget * buttons;
+ GtkWidget * top;
+ GtkWidget * fr;
+ GtkTreeModel * m;
+ GtkCellRenderer * r;
+ GtkTreeViewColumn * c;
+ GtkTreeSelection * sel;
+ struct tracker_page * page = g_new0( struct tracker_page, 1 );
+
+ page->gtor = gtor;
+
+ top = buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_RIGHT
+ ? gtk_hbox_new( FALSE, GUI_PAD )
+ : gtk_vbox_new( FALSE, GUI_PAD );
+
+ buttons = buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_RIGHT
+ ? gtk_vbox_new( FALSE, 0 )
+ : gtk_hbox_new( FALSE, 0 );
+
+ 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 );
+ g_object_unref( G_OBJECT( m ) );
+
+ w = gtk_button_new_from_stock( GTK_STOCK_ADD );
+ g_signal_connect( w, "clicked", G_CALLBACK( onTrackerAddClicked ), page );
+ gtk_box_pack_start( GTK_BOX( buttons ), 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( buttons ), w, FALSE, FALSE, 0 );
+ page->remove_button = w;
+ if( gtor )
+ {
+ 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( buttons ), 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( buttons ), w, FALSE, FALSE, 0 );
+ page->revert_button = w;
+ }
+
+ w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f );
+ gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
+
+ if( buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_TOP )
+ {
+ gtk_box_pack_start( GTK_BOX( top ), buttons, FALSE, FALSE, 0 );
+ gtk_box_pack_start_defaults( GTK_BOX( top ), fr );
+ }
+ else
+ {
+ gtk_box_pack_start_defaults( GTK_BOX( top ), fr );
+ gtk_box_pack_start( GTK_BOX( top ), buttons, FALSE, FALSE, 0 );
+ }
+
+ onTrackerSelectionChanged( sel, page );
+
+ g_object_set_data_full( G_OBJECT( top ), "page", page, g_free );
+ return top;
+}
+
+tr_tracker_info*
+tracker_list_get_trackers( GtkWidget * list,
+ int * trackerCount )
+{
+ struct tracker_page * page = g_object_get_data( G_OBJECT( list ), "page" );
+ GtkTreeModel * model = GTK_TREE_MODEL( page->store );
+ const int n = gtk_tree_model_iter_n_children( model, NULL );
+ tr_tracker_info * trackers;
+ int i = 0;
+ GtkTreeIter iter;
+
+ /* 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 );
+
+ *trackerCount = n;
+
+ return trackers;
+}
+
--- /dev/null
+#ifndef TRACKER_LIST_H
+#define TRACKER_LIST_H
+
+#include <gtk/gtkwidget.h>
+#include "tr-torrent.h"
+
+GtkWidget* tracker_list_new( TrTorrent * gtor,
+ GtkPositionType buttonsPosition );
+
+/**
+ * @return an array of tr_tracker_info's. It's the caller's responsibility
+ * to g_free() every announce in the array, then the array itself.
+ */
+tr_tracker_info* tracker_list_get_trackers( GtkWidget * list,
+ int * trackerCount );
+
+#endif
{
if( builder )
{
- uint32_t i;
- for( i=0; i<builder->fileCount; ++i )
- tr_free( builder->files[i].filename );
+ tr_file_index_t t;
+ int i;
+ for( t=0; t<builder->fileCount; ++t )
+ tr_free( builder->files[t].filename );
tr_free( builder->files );
tr_free( builder->top );
tr_free( builder->comment );
- tr_free( builder->announce );
+ for( i=0; i<builder->trackerCount; ++i )
+ tr_free( builder->trackers[i].announce );
+ tr_free( builder->trackers );
tr_free( builder->outputFile );
tr_free( builder );
}
static void
tr_realMakeMetaInfo ( tr_metainfo_builder * builder )
{
- int n = 5;
+ int i;
tr_benc top;
- const char * ann = builder->announce;
- if ( builder->comment && *builder->comment ) ++n;
- tr_bencInitDict( &top, n );
+ /* allow an empty set, but if URLs *are* listed, verify them. #814, #971 */
+ for( i=0; i<builder->trackerCount && !builder->result; ++i )
+ if( !tr_httpIsValidURL( builder->trackers[i].announce ) )
+ builder->result = TR_MAKEMETA_URL;
- tr_bencDictAddStr( &top, "announce", ann );
+ tr_bencInitDict( &top, 6 );
- /* if a URL was entered but it's invalid, don't allow it. #814, #971 */
- if( ann && *ann && !tr_httpIsValidURL( ann ) )
- builder->result = TR_MAKEMETA_URL;
+ if( !builder->result && builder->trackerCount )
+ {
+ int prevTier = -1;
+ tr_benc * tier = NULL;
+ tr_benc * announceList;
+
+ announceList = tr_bencDictAddList( &top, "announce-list", 0 );
+ for( i=0; i<builder->trackerCount; ++i ) {
+ if( prevTier != builder->trackers[i].tier ) {
+ prevTier = builder->trackers[i].tier;
+ tier = tr_bencListAddList( announceList, 0 );
+ }
+ tr_bencListAddStr( tier, builder->trackers[i].announce );
+ }
+
+ tr_bencDictAddStr( &top, "announce", builder->trackers[0].announce );
+ }
if( !builder->result && !builder->abortFlag )
{
}
/* cleanup */
- tr_bencFree( & top );
+ tr_bencFree( &top );
if( builder->abortFlag )
builder->result = TR_MAKEMETA_CANCELLED;
builder->isDone = 1;
}
void
-tr_makeMetaInfo( tr_metainfo_builder * builder,
- const char * outputFile,
- const char * announce,
- const char * comment,
- int isPrivate )
+tr_makeMetaInfo( tr_metainfo_builder * builder,
+ const char * outputFile,
+ const tr_tracker_info * trackers,
+ int trackerCount,
+ const char * comment,
+ int isPrivate )
{
+ int i;
tr_lock * lock;
/* free any variables from a previous run */
- tr_free( builder->announce );
+ for( i=0; i<builder->trackerCount; ++i )
+ tr_free( builder->trackers[i].announce );
+ tr_free( builder->trackers );
tr_free( builder->comment );
tr_free( builder->outputFile );
/* initialize the builder variables */
builder->abortFlag = 0;
builder->isDone = 0;
- builder->announce = tr_strdup( announce );
+ builder->trackerCount = trackerCount;
+ builder->trackers = tr_new0( tr_tracker_info, builder->trackerCount );
+ for( i=0; i<builder->trackerCount; ++i ) {
+ builder->trackers[i].tier = trackers[i].tier;
+ builder->trackers[i].announce = tr_strdup( trackers[i].announce );
+ }
builder->comment = tr_strdup( comment );
builder->isPrivate = isPrivate;
if( outputFile && *outputFile )
*** and cleaned up by tr_metaInfoBuilderFree()
**/
- char * announce;
+ tr_tracker_info * trackers;
+ int trackerCount;
char * comment;
char * outputFile;
int isPrivate;
tr_metaInfoBuilderFree( tr_metainfo_builder* );
/**
- * 'outputFile' if NULL, builder->top + ".torrent" will be used.
+ * @brief create a new .torrent file
*
* This is actually done in a worker thread, not the main thread!
* Otherwise the client's interface would lock up while this runs.
* It is the caller's responsibility to poll builder->isDone
* from time to time! When the worker thread sets that flag,
* the caller must pass the builder to tr_metaInfoBuilderFree().
+ *
+ * @param outputFile if NULL, builder->top + ".torrent" will be used.
+
+ * @param trackers An array of trackers, sorted by tier from first to last.
+ * NOTE: only the `tier' and `announce' fields are used.
+ *
+ * @param trackerCount size of the `trackers' array
*/
void
-tr_makeMetaInfo( tr_metainfo_builder * builder,
- const char * outputFile,
- const char * announce,
- const char * comment,
- int isPrivate );
+tr_makeMetaInfo( tr_metainfo_builder * builder,
+ const char * outputFile,
+ const tr_tracker_info * trackers,
+ int trackerCount,
+ const char * comment,
+ int isPrivate );
#endif