]> granicus.if.org Git - transmission/commitdiff
#224 (creating multi-tracker torrents): add multitracker support in libT's makemeta...
authorCharles Kerr <charles@transmissionbt.com>
Sun, 1 Jun 2008 05:36:23 +0000 (05:36 +0000)
committerCharles Kerr <charles@transmissionbt.com>
Sun, 1 Jun 2008 05:36:23 +0000 (05:36 +0000)
cli/transmissioncli.c
gtk/Makefile.am
gtk/details.c
gtk/makemeta-ui.c
gtk/tr-torrent.c
gtk/tracker-list.c [new file with mode: 0644]
gtk/tracker-list.h [new file with mode: 0644]
libtransmission/makemeta.c
libtransmission/makemeta.h

index 22398889f5d5d828c375ce29f2b55f9ae3cee1da..208f6183f5529dc739e100821093a75117f66d89 100644 (file)
@@ -176,7 +176,10 @@ main( int argc, char ** argv )
     {
         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( "." );
index d983e5da869a231126497ec827bf7a3c3c8c84c3..e8cbfa62d4e4e728dfc999ef20d7130900a0bb16 100644 (file)
@@ -31,6 +31,7 @@ noinst_HEADERS = \
     stats.h \
     sexy-icon-entry.h \
     torrent-cell-renderer.h \
+    tracker-list.h \
     tr-core.h \
     tr-core-dbus.h \
     tr-icon.h \
@@ -58,6 +59,7 @@ transmission_SOURCES = \
     sexy-icon-entry.c \
     stats.c \
     torrent-cell-renderer.c \
+    tracker-list.c \
     tr-core.c \
     tr-icon.c \
     tr-io.c \
index 33ca061e563bf6305eabd6f8b879bc2ca803765c..f76c809724e6e0ecb433b7272b86a34c54e9c5a4 100644 (file)
@@ -24,6 +24,7 @@
 #include "details.h"
 #include "file-list.h"
 #include "tr-torrent.h"
+#include "tracker-list.h"
 #include "hig.h"
 #include "util.h"
 
@@ -1099,213 +1100,14 @@ 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);
 
@@ -1314,74 +1116,8 @@ tracker_page_new( TrTorrent * 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" ) );
index 8cd41c8c90e2fb489e9d4b1eb3a9be3ad9b8a77a..badd62280990ab9b3bb93ce9fe3ba0e2f593d329 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "hig.h"
 #include "makemeta-ui.h"
+#include "tracker-list.h"
 #include "util.h"
 
 #define UPDATE_INTERVAL_MSEC 200
@@ -31,7 +32,7 @@ typedef struct
 {
     GtkWidget * size_lb;
     GtkWidget * pieces_lb;
-    GtkWidget * announce_entry;
+    GtkWidget * announce_list;
     GtkWidget * comment_entry;
     GtkWidget * progressbar;
     GtkWidget * private_check;
@@ -145,6 +146,9 @@ response_cb( GtkDialog* d, int response, gpointer user_data )
     char *tmp;
     char buf[1024];
     guint tag;
+    tr_tracker_info * trackers = NULL;
+    int i;
+    int trackerCount = 0;
 
     if( response != GTK_RESPONSE_ACCEPT )
     {
@@ -177,14 +181,21 @@ response_cb( GtkDialog* d, int response, gpointer user_data )
     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 );
 }
 
 /***
@@ -320,13 +331,16 @@ make_meta_ui( GtkWindow * parent, tr_handle * handle )
         
 
     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 );
index 465d68b9c3435411a8c0b71db670ff0b34dd05d2..437aa0f007df55e6ff0cede055826a76ec5828ce 100644 (file)
@@ -60,9 +60,9 @@ tr_torrent_init(GTypeInstance *instance, gpointer g_class UNUSED )
 }
 
 static int
-isDisposed( const TrTorrent * self )
+isDisposed( const TrTorrent * tor )
 {
-    return !self || !self->priv;
+    return !tor || !TR_IS_TORRENT( tor ) || !tor->priv;
 }
 
 static void
@@ -123,8 +123,6 @@ tr_torrent_get_type( void )
 tr_torrent *
 tr_torrent_handle(TrTorrent *tor)
 {
-    g_assert( TR_IS_TORRENT(tor) );
-
     return isDisposed( tor ) ? NULL : tor->priv->handle;
 }
 
diff --git a/gtk/tracker-list.c b/gtk/tracker-list.c
new file mode 100644 (file)
index 0000000..8e5d584
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * 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;
+}
+
diff --git a/gtk/tracker-list.h b/gtk/tracker-list.h
new file mode 100644 (file)
index 0000000..092a850
--- /dev/null
@@ -0,0 +1,17 @@
+#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
index 0a9679a8385cad9f0f931784867f8831889f5244..188eabbcd2acb550ce6af0981f7aa5054bfbe844 100644 (file)
@@ -168,13 +168,16 @@ tr_metaInfoBuilderFree( tr_metainfo_builder * builder )
 {
     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 );
     }
@@ -341,18 +344,33 @@ makeInfoDict ( tr_benc              * dict,
 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 )
     {
@@ -374,7 +392,7 @@ tr_realMakeMetaInfo ( tr_metainfo_builder * builder )
     }
 
     /* cleanup */
-    tr_bencFree( & top );
+    tr_bencFree( &top );
     if( builder->abortFlag )
         builder->result = TR_MAKEMETA_CANCELLED;
     builder->isDone = 1;
@@ -430,23 +448,32 @@ static void workerFunc( void * user_data )
 }
 
 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 )
index 7e9bdc7d2df67544595ce59a2f78171dd17d9afb..63cc05dca3eb958e4af696a196b67b4240c4ad3f 100644 (file)
@@ -54,7 +54,8 @@ typedef struct tr_metainfo_builder
     ***  and cleaned up by tr_metaInfoBuilderFree()
     **/
 
-    char * announce;
+    tr_tracker_info  * trackers;
+    int trackerCount;
     char * comment;
     char * outputFile;
     int isPrivate;
@@ -95,7 +96,7 @@ void
 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.
@@ -103,13 +104,21 @@ tr_metaInfoBuilderFree( tr_metainfo_builder* );
  * 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