"seedIdleMode" | number which seeding inactivity to use. See tr_inactvelimit
"seedRatioLimit" | double torrent-level seeding ratio
"seedRatioMode" | number which ratio to use. See tr_ratiolimit
- "trackerAdd" | object (see below)
- "trackerEdit" | object (see below)
- "trackerRemove" | object (see below)
+ "trackerAdd" | array strings of URLs to add
+ "trackerRemove" | array strings of URLs to remove
+ "trackerReplace" | array pairs of old/new announce URLs
"uploadLimit" | number maximum upload speed (KBps)
"uploadLimited" | boolean true if "uploadLimit" is honored
- |
- ----------------------+---------------------------------+
- trackerAdd | an object containing: |
- +-----------------------+---------+
- | announce | string | announce URL of the tracker
- | tier (optional) | number | tier to add the tracker to
- ----------------------+---------------------------------+
- trackerEdit | an object containing: |
- +-----------------------+---------+
- | announce (or id) | string | announce URL of the tracker to modify
- | id (or announce) | number | trackerId of the tracker to modify (see trackerStats)
- +-----------------------+---------+
- | announce-new | string | new announce URL for the tracker
- | tier | number | tier to change the tracker to
- ----------------------+---------------------------------+
- trackerRemove | an object containing: |
- +-----------------------+---------+
- | announce (or id) | string | announce URL of the tracker to remove
- | id (or announce) | number | trackerId of the tracker to remove (see trackerStats)
- +-----------------------+---------+
Just as an empty "ids" value is shorthand for "all ids", using an empty array
for "files-wanted", "files-unwanted", "priority-high", "priority-low", or
}
static tr_bool
-findTrackerById( const tr_info * inf,
- uint32_t id,
- int * index )
+findAnnounceUrl( const tr_tracker_info * t, int n, const char * url, int * pos )
{
int i;
tr_bool found = FALSE;
- for( i = 0; i < inf->trackerCount; ++i )
+ for( i=0; i<n; ++i )
{
- const tr_tracker_info * t = &inf->trackers[i];
- if( t->id == id )
+ if( !strcmp( t[i].announce, url ) )
{
- if( index ) *index = i;
found = TRUE;
+ if( pos ) *pos = i;
break;
}
}
return found;
}
-static tr_bool
-findTrackerByURL( const tr_info * inf,
- const char * url,
- int * index )
+static int
+copyTrackers( tr_tracker_info * tgt, const tr_tracker_info * src, int n )
{
int i;
- tr_bool found = FALSE;
-
- for( i = 0; i < inf->trackerCount; ++i )
+ int maxTier = -1;
+
+ for( i=0; i<n; ++i )
{
- const tr_tracker_info * t = &inf->trackers[i];
- if( !strcmp( t->announce, url ) )
- {
- if( index ) *index = i;
- found = TRUE;
- break;
- }
+ tgt[i].tier = src[i].tier;
+ tgt[i].announce = tr_strdup( src[i].announce );
+ maxTier = MAX( maxTier, src[i].tier );
}
- return found;
+ return maxTier;
+}
+
+static void
+freeTrackers( tr_tracker_info * trackers, int n )
+{
+ int i;
+
+ for( i=0; i<n; ++i )
+ tr_free( trackers[i].announce );
+
+ tr_free( trackers );
}
static const char*
-addTracker( tr_torrent * tor,
- tr_benc * tracker )
+addTrackerUrls( tr_torrent * tor, tr_benc * urls )
{
int i;
- int64_t tmp;
- tr_bool duplicate = FALSE;
- const char * errmsg = NULL;
- const char * announce;
+ int n;
+ int tier;
+ tr_benc * val;
+ tr_tracker_info * trackers;
+ tr_bool changed = FALSE;
const tr_info * inf = tr_torrentInfo( tor );
+ const char * errmsg = NULL;
- if( !tr_bencDictFindStr( tracker, "announce", &announce ) )
- return "no announce url supplied";
-
- duplicate = findTrackerByURL( inf, announce, NULL );
+ /* make a working copy of the existing announce list */
+ n = inf->trackerCount;
+ trackers = tr_new0( tr_tracker_info, n + tr_bencListSize( urls ) );
+ tier = copyTrackers( trackers, inf->trackers, n );
- if( !duplicate )
+ /* and add the new ones */
+ i = 0;
+ while(( val = tr_bencListChild( urls, i++ ) ))
{
- int tier, trackerCount;
- tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount + 1 );
-
- if( tr_bencDictFindInt( tracker, "tier", &tmp ) )
- tier = (int)tmp;
- else
- tier = -1;
+ const char * announce = NULL;
- for( i = 0; i < inf->trackerCount; ++i )
+ if( tr_bencGetStr( val, &announce )
+ && tr_urlIsValid( announce )
+ && !findAnnounceUrl( trackers, n, announce, NULL ) )
{
- const tr_tracker_info * t = &inf->trackers[i];
- trackers[i].tier = t->tier;
- trackers[i].announce = tr_strdup( t->announce );
+ trackers[n].tier = ++tier; /* add a new tier */
+ trackers[n].announce = tr_strdup( announce );
+ ++n;
+ changed = TRUE;
}
- trackers[i].tier = tier < 0 ? trackers[i-1].tier + 1 : tier;
- trackers[i].announce = tr_strdup( announce );
- trackerCount = inf->trackerCount + 1;
-
- if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
- errmsg = "tracker URL was invalid";
-
- for( i = 0; i < trackerCount; ++i )
- tr_free( trackers[i].announce );
- tr_free( trackers );
}
- else
- errmsg = "tracker already exists";
+ if( !changed )
+ errmsg = "invalid argument";
+ else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+ errmsg = "error setting announce list";
+
+ freeTrackers( trackers, n );
return errmsg;
}
static const char*
-editTracker( tr_torrent * tor,
- tr_benc * tracker )
+replaceTrackerUrls( tr_torrent * tor, tr_benc * urls )
{
- int trackerIndex;
- int64_t tmp;
- tr_bool found = FALSE;
- const char * errmsg = NULL;
- const char * announce;
+ int i;
+ tr_benc * pair[2];
+ tr_tracker_info * trackers;
+ tr_bool changed = FALSE;
const tr_info * inf = tr_torrentInfo( tor );
+ const int n = inf->trackerCount;
+ const char * errmsg = NULL;
- if( tr_bencDictFindInt( tracker, "id", &tmp ) )
- found = findTrackerById( inf, (uint32_t)tmp, &trackerIndex );
- else if( tr_bencDictFindStr( tracker, "announce", &announce ) )
- found = findTrackerByURL( inf, announce, &trackerIndex );
- else
- errmsg = "no tracker supplied";
+ /* make a working copy of the existing announce list */
+ trackers = tr_new0( tr_tracker_info, n );
+ copyTrackers( trackers, inf->trackers, n );
- if( found )
+ /* make the substitutions... */
+ i = 0;
+ while(((pair[0] = tr_bencListChild(urls,i))) &&
+ ((pair[1] = tr_bencListChild(urls,i+1))))
{
- int tier;
- const char * new;
- tr_bool rename = FALSE;
- tr_bool move = FALSE;
-
- if( tr_bencDictFindStr( tracker, "announce-new", &new ) )
+ const char * oldval;
+ const char * newval;
+
+ if( tr_bencGetStr( pair[0], &oldval )
+ && tr_bencGetStr( pair[1], &newval )
+ && strcmp( oldval, newval )
+ && tr_urlIsValid( newval )
+ && findAnnounceUrl( trackers, n, oldval, &i ) )
{
- rename = !findTrackerByURL( inf, new, NULL );
- if( !rename )
- errmsg = "tracker already exists";
- }
- if( tr_bencDictFindInt( tracker, "tier", &tmp ) )
- {
- tier = (int)tmp;
- move = TRUE;
+ tr_free( trackers[i].announce );
+ trackers[i].announce = tr_strdup( newval );
+ changed = TRUE;
}
- if( ( rename || move ) && !errmsg )
- {
- int i, trackerCount;
- tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount );
-
- for( i = 0; i < inf->trackerCount; ++i )
- {
- const tr_tracker_info * t = &inf->trackers[i];
- if( i != trackerIndex )
- {
- trackers[i].tier = t->tier;
- trackers[i].announce = tr_strdup( t->announce );
- }
- else
- {
- trackers[i].tier = move ? tier : t->tier;
- trackers[i].announce = tr_strdup( rename ? new : t->announce );
- }
- }
- trackerCount = i;
-
- if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
- errmsg = "error setting announce list";
-
- for( i = 0; i < trackerCount; ++i )
- tr_free( trackers[i].announce );
- tr_free( trackers );
- }
- else if( !errmsg )
- errmsg = "no operation supplied";
+ i += 2;
}
- else
- errmsg = "tracker doesn't exists";
+ if( !changed )
+ errmsg = "invalid argument";
+ else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+ errmsg = "error setting announce list";
+
+ freeTrackers( trackers, n );
return errmsg;
}
static const char*
-removeTracker( tr_torrent * tor,
- tr_benc * tracker )
+removeTrackerUrls( tr_torrent * tor, tr_benc * urls )
{
- int trackerIndex;
- int64_t tmp;
- tr_bool found = FALSE;
- const char * errmsg = NULL;
- const char * announce;
+ int i;
+ int n;
+ tr_benc * val;
+ tr_tracker_info * trackers;
+ tr_bool changed = FALSE;
const tr_info * inf = tr_torrentInfo( tor );
+ const char * errmsg = NULL;
- if( tr_bencDictFindInt( tracker, "id", &tmp ) )
- found = findTrackerById( inf, (uint32_t)tmp, &trackerIndex );
- else if( tr_bencDictFindStr( tracker, "announce", &announce ) )
- found = findTrackerByURL( inf, announce, &trackerIndex );
- else
- errmsg = "no tracker supplied";
+ /* make a working copy of the existing announce list */
+ n = inf->trackerCount;
+ trackers = tr_new0( tr_tracker_info, n );
+ copyTrackers( trackers, inf->trackers, n );
- if( found )
+ /* remove the ones specified in the urls list */
+ i = 0;
+ while(( val = tr_bencListChild( urls, i++ )))
{
- int i, j, trackerCount;
- tr_tracker_info * trackers = tr_new0( tr_tracker_info, inf->trackerCount - 1 );
-
- for( i = 0, j = 0; i < inf->trackerCount; ++i )
+ int pos;
+ const char * url;
+ if( tr_bencGetStr( val, &url ) && findAnnounceUrl( trackers, n, url, &pos ) )
{
- if( i != trackerIndex )
- {
- const tr_tracker_info * t = &inf->trackers[i];
- trackers[j].tier = t->tier;
- trackers[j].announce = tr_strdup( t->announce );
- ++j;
- }
+ tr_removeElementFromArray( trackers, pos, sizeof( tr_tracker_info ), n-- );
+ changed = TRUE;
}
- trackerCount = j;
-
- if( !tr_torrentSetAnnounceList( tor, trackers, trackerCount ) )
- errmsg = "error setting announce list";
-
- for( i = 0; i < trackerCount; ++i )
- tr_free( trackers[i].announce );
- tr_free( trackers );
}
- else
- errmsg = "tracker doesn't exists";
+ if( !changed )
+ errmsg = "invalid argument";
+ else if( !tr_torrentSetAnnounceList( tor, trackers, n ) )
+ errmsg = "error setting announce list";
+
+ freeTrackers( trackers, n );
return errmsg;
}
int64_t tmp;
double d;
tr_benc * files;
- tr_benc * tracker;
+ tr_benc * urls;
tr_bool boolVal;
tr_torrent * tor = torrents[i];
tr_torrentSetRatioLimit( tor, d );
if( tr_bencDictFindInt( args_in, "seedRatioMode", &tmp ) )
tr_torrentSetRatioMode( tor, tmp );
- if( !errmsg && tr_bencDictFindDict( args_in, "trackerAdd", &tracker ) )
- errmsg = addTracker( tor, tracker );
- if( !errmsg && tr_bencDictFindDict( args_in, "trackerEdit", &tracker ) )
- errmsg = editTracker( tor, tracker );
- if( !errmsg && tr_bencDictFindDict( args_in, "trackerRemove", &tracker ) )
- errmsg = removeTracker( tor, tracker );
+ if( !errmsg && tr_bencDictFindList( args_in, "trackerAdd", &urls ) )
+ errmsg = addTrackerUrls( tor, urls );
+ if( !errmsg && tr_bencDictFindList( args_in, "trackerRemove", &urls ) )
+ errmsg = removeTrackerUrls( tor, urls );
+ if( !errmsg && tr_bencDictFindList( args_in, "trackerReplace", &urls ) )
+ errmsg = replaceTrackerUrls( tor, urls );
notify( session, TR_RPC_TORRENT_CHANGED, tor );
}
#include <cassert>
#include <ctime>
-#include <iostream>
#include <QCheckBox>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
+#include <QItemSelectionModel>
#include <QLabel>
+#include <QList>
+#include <QMap>
#include <QMessageBox>
#include <QPushButton>
#include <QRadioButton>
#include <QResizeEvent>
#include <QSpinBox>
+#include <QStringList>
#include <QStyle>
#include <QTabWidget>
#include <QTextBrowser>
#include "squeezelabel.h"
#include "torrent.h"
#include "torrent-model.h"
+#include "tracker-delegate.h"
+#include "tracker-model.h"
class Prefs;
class Session;
layout->addWidget( buttons );
QWidget::setAttribute( Qt::WA_DeleteOnClose, true );
+ QList<int> initKeys;
+ initKeys << Prefs :: SHOW_TRACKER_SCRAPES;
+ foreach( int key, initKeys )
+ refreshPref( key );
+
connect( &myTimer, SIGNAL(timeout()), this, SLOT(onTimer()));
+ connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(int)) );
onTimer( );
myTimer.setSingleShot( false );
}
myFileTreeView->clear( );
-
myIds = ids;
// listen to the new torrents
onTimer( );
}
+void
+Details :: refreshPref( int key )
+{
+ QString str;
+
+ switch( key )
+ {
+ case Prefs::SHOW_TRACKER_SCRAPES:
+ myTrackerDelegate->setShowMore( myPrefs.getBool( key ) );
+ myTrackerView->update( );
+ break;
+
+ default:
+ break;
+ }
+}
+
+
/***
****
***/
void
Details :: onTimer( )
+{
+ getNewData( );
+}
+
+void
+Details :: getNewData( )
{
if( !myIds.empty( ) )
{
myIdleSpin->blockSignals( false );
}
- // tracker tab
- //
- QMap<QString,QTreeWidgetItem*> trackerTiers;
- QMap<QString,QTreeWidgetItem*> trackerItems;
- const time_t now( time( 0 ) );
- const bool showScrape = myPrefs.getBool( Prefs::SHOW_TRACKER_SCRAPES );
- foreach( const Torrent * t, torrents )
- {
- const QString idStr( QString::number( t->id( ) ) );
- const TrackerStatsList trackerStats = t->trackerStats( );
-
- foreach( const TrackerStat& trackerStat, trackerStats )
- {
- QFont font;
- QString str;
- const QString tierKey( QString::number(trackerStat.tier) );
- QTreeWidgetItem * tier = (QTreeWidgetItem*) myTrackerTiers.value( tierKey, 0 );
-
- if( tier == 0 ) // check if has tier been created this pass
- tier = (QTreeWidgetItem*) trackerTiers.value( tierKey, 0 );
-
- if( tier == 0 ) // new tier
- {
- QFont tierFont;
- tier = new QTreeWidgetItem( myTrackerTree );
- myTrackerTree->addTopLevelItem( tier );
- str = "Tier: " + QString::number( trackerStat.tier + 1 );
- tier->setText( 0, str );
- tierFont.setBold( true );
- tier->setFont( 0, tierFont );
- }
-
- const QString key( idStr + tierKey + ":" + QString::number( trackerStat.id ) );
- QTreeWidgetItem * item = (QTreeWidgetItem*) myTrackerItems.value( key, 0 );
-
- if( item == 0 ) // new tracker
- {
- item = new QTreeWidgetItem( tier );
- tier->addChild( item );
- if( tier->childCount() == 1 )
- tier->setExpanded( true );
- }
- str = trackerStat.host;
+ ///
+ /// Tracker tab
+ ///
- if( trackerStat.isBackup )
- {
- font.setItalic( true );
- if( showScrape )
- {
- str += "\n";
- str += "Tracker will be used as a backup";
- }
- }
- else
- {
- font.setItalic( false );
- if( trackerStat.hasAnnounced )
- {
- const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceTime ) );
- str += "\n";
- if( trackerStat.lastAnnounceSucceeded )
- {
- str += tr( "Got a list of %1 peers %2 ago" )
- .arg( trackerStat.lastAnnouncePeerCount )
- .arg( tstr );
- }
- else if( trackerStat.lastAnnounceTimedOut )
- {
- str += tr( "Peer list request timed out %1 ago; will retry" )
- .arg( tstr );
- }
- else
- {
- str += tr( "Got an error %1 ago" )
- .arg( tstr );
- }
- }
- switch( trackerStat.announceState )
- {
- case TR_TRACKER_INACTIVE:
- if( trackerStat.hasAnnounced )
- {
- str += "\n";
- str += tr( "No updates scheduled" );
- }
- break;
- case TR_TRACKER_WAITING:
- {
- const QString tstr( timeToStringRounded( trackerStat.nextAnnounceTime - now ) );
- str += "\n";
- str += tr( "Asking for more peers in %1" )
- .arg( tstr );
- }
- break;
- case TR_TRACKER_QUEUED:
- str += "\n";
- str += tr( "Queued to ask for more peers" );
- break;
- case TR_TRACKER_ACTIVE:
- {
- const QString tstr( timeToStringRounded( now - trackerStat.lastAnnounceStartTime ) );
- str += "\n";
- str += tr( "Asking for more peers now... %1" )
- .arg( tstr );
- }
- break;
- }
- if( showScrape )
- {
- if( trackerStat.hasScraped )
- {
- const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeTime ) );
- str += "\n";
- if( trackerStat.lastScrapeSucceeded )
- {
- str += tr( "Tracker had %1 seeders and %2 leechers %3 ago" )
- .arg( trackerStat.seederCount )
- .arg( trackerStat.leecherCount )
- .arg( tstr );
- }
- else
- {
- str += tr( "Got a scrape error %1 ago" )
- .arg( tstr );
- }
- }
- switch( trackerStat.scrapeState )
- {
- case TR_TRACKER_INACTIVE:
- break;
- case TR_TRACKER_WAITING:
- {
- const QString tstr( timeToStringRounded( trackerStat.nextScrapeTime - now ) );
- str += "\n";
- str += tr( "Asking for peer counts in %1" )
- .arg( tstr );
- }
- break;
- case TR_TRACKER_QUEUED:
- str += "\n";
- str += tr( "Queued to ask for peer counts" );
- break;
- case TR_TRACKER_ACTIVE:
- {
- const QString tstr( timeToStringRounded( now - trackerStat.lastScrapeStartTime ) );
- str += "\n";
- str += tr( "Asking for peer counts now... %1" )
- .arg( tstr );
- }
- break;
- }
- }
- }
- item->setText( 0, str );
- item->setFont( 0, font );
- item->setData( 0, TRACKERID, trackerStat.id );
- item->setData( 0, TRACKERURL, trackerStat.announce );
- item->setData( 0, TRACKERTIER, trackerStat.tier );
- item->setData( 0, TORRENTID, t->id() );
-
- tier->setData( 0, TRACKERID, -1 );
- tier->setData( 0, TRACKERURL, QString() );
- tier->setData( 0, TRACKERTIER, trackerStat.tier );
- tier->setData( 0, TORRENTID, torrents.count() > 1 ? -1 : t->id() );
-
- trackerTiers.insert( tierKey, tier );
- trackerItems.insert( key, item );
- }
- }
- QList<QTreeWidgetItem*> tierList = trackerTiers.values();
- QList<QTreeWidgetItem*> itemList = trackerItems.values();
- for( int i = 0; i < myTrackerTree->topLevelItemCount(); ++i )
- {
- QTreeWidgetItem * tier = myTrackerTree->topLevelItem( i );
- for( int j = 0; j < tier->childCount(); ++j )
- {
- if( !itemList.contains( tier->child( j ) ) ) // tracker has disappeared
- delete tier->takeChild( j-- );
- }
- if( !tierList.contains( tier ) ) // tier has disappeared
- delete myTrackerTree->takeTopLevelItem( i-- );
- }
- myTrackerTiers = trackerTiers;
- myTrackerItems = trackerItems;
+ myTrackerModel->refresh( myModel, myIds );
///
/// Peers tab
Details :: onHonorsSessionLimitsToggled( bool val )
{
mySession.torrentSet( myIds, "honorsSessionLimits", val );
+ getNewData( );
}
void
Details :: onDownloadLimitedToggled( bool val )
{
mySession.torrentSet( myIds, "downloadLimited", val );
+ getNewData( );
}
void
Details :: onDownloadLimitChanged( int val )
{
mySession.torrentSet( myIds, "downloadLimit", val );
+ getNewData( );
}
void
Details :: onUploadLimitedToggled( bool val )
{
mySession.torrentSet( myIds, "uploadLimited", val );
+ getNewData( );
}
void
Details :: onUploadLimitChanged( int val )
{
mySession.torrentSet( myIds, "uploadLimit", val );
+ getNewData( );
}
void
{
const int val = myIdleCombo->itemData( index ).toInt( );
mySession.torrentSet( myIds, "seedIdleMode", val );
+ getNewData( );
}
void
Details :: onIdleLimitChanged( int val )
{
mySession.torrentSet( myIds, "seedIdleLimit", val );
+ getNewData( );
}
void
Details :: onRatioLimitChanged( double val )
{
mySession.torrentSet( myIds, "seedRatioLimit", val );
+ getNewData( );
}
void
Details :: onMaxPeersChanged( int val )
{
mySession.torrentSet( myIds, "peer-limit", val );
+ getNewData( );
}
void
{
const int priority = myBandwidthPriorityCombo->itemData(index).toInt( );
mySession.torrentSet( myIds, "bandwidthPriority", priority );
+ getNewData( );
}
}
void
Details :: onTrackerSelectionChanged( )
{
- const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
- if( items.count() == 1 )
- myEditTrackerButton->setEnabled( items.first()->data( 0, TRACKERID ).toInt() >= 0 );
- else
- myEditTrackerButton->setEnabled( false );
- myRemoveTrackerButton->setEnabled( !items.isEmpty() );
+ const int selectionCount = myTrackerView->selectionModel()->selectedRows().size();
+ myEditTrackerButton->setEnabled( selectionCount == 1 );
+ myRemoveTrackerButton->setEnabled( selectionCount > 0 );
}
-bool
-Details :: findTrackerByURL( const QString& url, int torId )
+void
+Details :: onAddTrackerClicked( )
{
- bool duplicate = false;
- foreach( QTreeWidgetItem * tracker, myTrackerItems.values() )
+ bool ok = false;
+ const QString url = QInputDialog::getText( this,
+ tr( "Add URL " ),
+ tr( "Add tracker announce URL:" ),
+ QLineEdit::Normal, QString(), &ok );
+ if( !ok )
{
- if( tracker->data( 0, TRACKERURL ).toString() == url &&
- ( torId == -1 || tracker->data( 0, TORRENTID ).toInt() == torId ) )
- {
- duplicate = true;
- break;
- }
+ // user pressed "cancel" -- noop
}
- return duplicate;
-}
-
-void
-Details :: onAddTrackerPushed( )
-{
- const QString urlString = QInputDialog::getText( this,
- tr( "Add tracker announce URL " ),
- NULL );
- if( !urlString.isEmpty() )
+ else if( !QUrl(url).isValid( ) )
{
- if( !findTrackerByURL( urlString, -1 ) )
- {
- QByteArray url = urlString.toUtf8();
- tr_benc top;
+ QMessageBox::warning( this, tr( "Error" ), tr( "Invalid URL \"%1\"" ).arg( url ) );
+ }
+ else
+ {
+ QSet<int> ids;
- tr_bencInitDict( &top, 1 );
- tr_bencDictAddStr( &top, "announce", url );
+ foreach( int id, myIds )
+ if( myTrackerModel->find( id, url ) == -1 )
+ ids.insert( id );
- mySession.torrentSet( myIds, "trackerAdd", &top );
+ if( ids.empty( ) ) // all the torrents already have this tracker
+ {
+ QMessageBox::warning( this, tr( "Error" ), tr( "Tracker already exists." ) );
}
else
- QMessageBox::warning( this, "Error", "Tracker already exists." );
+ {
+ QStringList urls;
+ urls << url;
+ mySession.torrentSet( ids, "trackerAdd", urls );
+ getNewData( );
+ }
}
}
void
-Details :: onEditTrackerPushed( )
+Details :: onEditTrackerClicked( )
{
- const QTreeWidgetItem * item = myTrackerTree->selectedItems().first();
- const QString urlString = QInputDialog::getText( this,
- tr( "Edit tracker announce URL " ),
- NULL,
- QLineEdit::Normal,
- item->data( 0, TRACKERURL ).toString() );
- if( !urlString.isEmpty() )
+ QItemSelectionModel * selectionModel = myTrackerView->selectionModel( );
+ QModelIndexList selectedRows = selectionModel->selectedRows( );
+ assert( selectedRows.size( ) == 1 );
+ QModelIndex i = selectionModel->currentIndex( );
+ const TrackerInfo trackerInfo = myTrackerModel->data( i, TrackerModel::TrackerRole ).value<TrackerInfo>();
+
+ bool ok = false;
+ const QString newval = QInputDialog::getText( this,
+ tr( "Edit URL " ),
+ tr( "Edit tracker announce URL:" ),
+ QLineEdit::Normal,
+ trackerInfo.st.announce, &ok );
+
+ if( !ok )
{
- const int torId = item->data( 0, TORRENTID ).toInt();
- if( !findTrackerByURL( urlString, torId ) )
- {
- QByteArray url = urlString.toUtf8();
- QSet<int> ids;
- tr_benc top;
+ // user pressed "cancel" -- noop
+ }
+ else if( !QUrl(newval).isValid( ) )
+ {
+ QMessageBox::warning( this, tr( "Error" ), tr( "Invalid URL \"%1\"" ).arg( newval ) );
+ }
+ else
+ {
+ QSet<int> ids;
+ ids << trackerInfo.torrentId;
- ids << torId;
- tr_bencInitDict( &top, 2 );
- tr_bencDictAddStr( &top, "announce", item->data( 0, TRACKERURL ).toByteArray() );
- tr_bencDictAddStr( &top, "announce-new", url );
+ QStringList urls;
+ urls << trackerInfo.st.announce;
+ urls << newval;
- mySession.torrentSet( ids, "trackerEdit", &top );
- }
- else
- QMessageBox::warning( this, "Error", "Tracker already exists." );
+ mySession.torrentSet( ids, "trackerReplace", urls );
+ getNewData( );
}
}
void
-Details :: removeTracker( const QTreeWidgetItem * item )
+Details :: onRemoveTrackerClicked( )
{
- QByteArray url = item->data( 0, TRACKERURL ).toByteArray();
- const int torId = item->data( 0, TORRENTID ).toInt();
- QSet<int> ids;
- tr_benc top;
-
- ids << torId;
- tr_bencInitDict( &top, 1 );
- tr_bencDictAddStr( &top, "announce", url );
-
- mySession.torrentSet( ids, "trackerRemove", &top );
-}
+ // make a map of torrentIds to announce URLs to remove
+ QItemSelectionModel * selectionModel = myTrackerView->selectionModel( );
+ QModelIndexList selectedRows = selectionModel->selectedRows( );
+ QMap<int,QStringList> torrentId_to_urls;
+ foreach( QModelIndex i, selectedRows )
+ {
+ const TrackerInfo inf = myTrackerModel->data( i, TrackerModel::TrackerRole ).value<TrackerInfo>();
+ torrentId_to_urls[ inf.torrentId ].append( inf.st.announce );
+ }
-void
-Details :: onRemoveTrackerPushed( )
-{
- const QList<QTreeWidgetItem*> items = myTrackerTree->selectedItems();
- QSet<int> removedTiers;
- foreach( const QTreeWidgetItem * item, items ) {
- const bool isTier = item->data( 0, TRACKERID ).toInt() == -1;
- const int curTier = item->data( 0, TRACKERTIER ).toInt();
- if( isTier )
- {
- removedTiers << curTier;
- for( int i = 0; i < item->childCount(); ++i )
- removeTracker( item->child( i ) );
- }
- else if( !removedTiers.contains( curTier ) ) // skip trackers removed by clearing a tier
- removeTracker( item );
+ // batch all of a tracker's torrents into one command
+ foreach( int id, torrentId_to_urls.keys( ) )
+ {
+ QSet<int> ids;
+ ids << id;
+ mySession.torrentSet( ids, "trackerRemove", torrentId_to_urls.value( id ) );
+ getNewData( );
}
}
v2->setSpacing( HIG::PAD );
- QStringList headers;
- headers << tr("Trackers");
- myTrackerTree = new QTreeWidget;
- myTrackerTree->setHeaderLabels( headers );
- myTrackerTree->setSelectionMode( QTreeWidget::ExtendedSelection );
- myTrackerTree->setRootIsDecorated( false );
- myTrackerTree->setIndentation( 2 );
- myTrackerTree->setItemsExpandable( false );
- myTrackerTree->setTextElideMode( Qt::ElideRight );
- myTrackerTree->setAlternatingRowColors( true );
- connect( myTrackerTree, SIGNAL(itemSelectionChanged()), this, SLOT(onTrackerSelectionChanged()));
- h->addWidget( myTrackerTree, 1 );
+ myTrackerView = new QTreeView;
+ myTrackerView->setModel( myTrackerModel = new TrackerModel );
+ myTrackerView->setHeaderHidden( true );
+ myTrackerView->setSelectionMode( QTreeWidget::ExtendedSelection );
+ myTrackerView->setRootIsDecorated( false );
+ myTrackerView->setIndentation( 2 );
+ myTrackerView->setItemsExpandable( false );
+ myTrackerView->setAlternatingRowColors( true );
+ myTrackerView->setItemDelegate( myTrackerDelegate = new TrackerDelegate( ) );
+ connect( myTrackerView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(onTrackerSelectionChanged()));
+ h->addWidget( myTrackerView, 1 );
p = new QPushButton();
p->setIcon( getStockIcon( "list-add", QStyle::SP_DialogOpenButton ) );
p->setToolTip( "Add Tracker" );
myAddTrackerButton = p;
v2->addWidget( p, 1 );
- connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerPushed()));
+ connect( p, SIGNAL(clicked(bool)), this, SLOT(onAddTrackerClicked()));
p = new QPushButton();
p->setIcon( getStockIcon( "document-properties", QStyle::SP_DesktopIcon ) );
p->setEnabled( false );
myEditTrackerButton = p;
v2->addWidget( p, 1 );
- connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerPushed()));
+ connect( p, SIGNAL(clicked(bool)), this, SLOT(onEditTrackerClicked()));
p = new QPushButton();
p->setIcon( getStockIcon( "list-remove", QStyle::SP_TrashIcon ) );
p->setEnabled( false );
myRemoveTrackerButton = p;
v2->addWidget( p, 1 );
- connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerPushed()));
+ connect( p, SIGNAL(clicked(bool)), this, SLOT(onRemoveTrackerClicked()));
v2->addStretch( 1 );
default: key = "priority-normal"; break;
}
mySession.torrentSet( myIds, key, indices.toList( ) );
+ getNewData( );
}
void
{
QString key( wanted ? "files-wanted" : "files-unwanted" );
mySession.torrentSet( myIds, key, indices.toList( ) );
+ getNewData( );
}
class Session;
class Torrent;
class TorrentModel;
+class TrackerDelegate;
+class TrackerModel;
class Details: public QDialog
{
Q_OBJECT
private:
- enum
- {
- TRACKERID = Qt::UserRole,
- TRACKERURL,
- TRACKERTIER,
- TORRENTID
- };
+ void getNewData( );
private slots:
void onTorrentChanged( );
QString timeToStringRounded( int seconds );
QString trimToDesiredWidth( const QString& str );
void enableWhenChecked( QCheckBox *, QWidget * );
- bool findTrackerByURL( const QString& url, int torId );
- void removeTracker( const QTreeWidgetItem * item );
private:
Session& mySession;
QLabel * myAnnounceResponseLabel;
QLabel * myAnnounceManualLabel;
- QTreeWidget * myTrackerTree;
+ TrackerModel * myTrackerModel;
+ TrackerDelegate * myTrackerDelegate;
+ QTreeView * myTrackerView;
+ //QMap<QString,QTreeWidgetItem*> myTrackerTiers;
+ //QMap<QString,QTreeWidgetItem*> myTrackerItems;
+
QTreeWidget * myPeerTree;
- QMap<QString,QTreeWidgetItem*> myTrackerTiers;
- QMap<QString,QTreeWidgetItem*> myTrackerItems;
QMap<QString,QTreeWidgetItem*> myPeers;
+
QWidgetList myWidgets;
FileTreeView * myFileTreeView;
private slots:
+ void refreshPref( int key );
void onBandwidthPriorityChanged( int );
void onFilePriorityChanged( const QSet<int>& fileIndices, int );
void onFileWantedChanged( const QSet<int>& fileIndices, bool );
void onIdleLimitChanged( int );
void onShowTrackerScrapesToggled( bool );
void onTrackerSelectionChanged( );
- void onAddTrackerPushed( );
- void onEditTrackerPushed( );
- void onRemoveTrackerPushed( );
+ void onAddTrackerClicked( );
+ void onEditTrackerClicked( );
+ void onRemoveTrackerClicked( );
void onMaxPeersChanged( int );
void refresh( );
};
if( !myPixmaps.contains( host ) )
{
// add a placholder s.t. we only ping the server once per session
- myPixmaps.insert( host, QPixmap( ) );
+ QPixmap tmp( 16, 16 );
+ tmp.fill( Qt::transparent );
+ myPixmaps.insert( host, tmp );
// try to download the favicon
const QString path = "http://" + host + "/favicon.";
{
Q_OBJECT;
+ public:
+
+ static QString getHost( const QUrl& url );
+
public:
Favicons();
QNetworkAccessManager * myNAM;
QMap<QString,QPixmap> myPixmaps;
- QString getHost( const QUrl& url );
-
QString getCacheDir( );
void ensureCacheDirHasBeenScanned( );
relocate.cc session.cc session-dialog.cc squeezelabel.cc \
stats-dialog.cc torrent.cc torrent-delegate.cc \
torrent-delegate-min.cc torrent-filter.cc torrent-model.cc \
- triconpushbutton.cc utils.cc watchdir.cc
+ tracker-delegate.cc tracker-model.cc triconpushbutton.cc \
+ utils.cc watchdir.cc
HEADERS += $$replace(SOURCES, .cc, .h)
HEADERS += speed.h types.h
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSet>
+#include <QStringList>
#include <QStyle>
#include <QTextStream>
}
void
-Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& value )
+Session :: torrentSet( const QSet<int>& ids, const QString& key, const QStringList& value )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-set" );
- tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
+ tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
addOptionalIds( args, ids );
tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
- foreach( int i, value )
- tr_bencListAddInt( list, i );
+ foreach( const QString str, value )
+ tr_bencListAddStr( list, str.toUtf8().constData() );
exec( &top );
tr_bencFree( &top );
}
void
-Session :: torrentSet( const QSet<int>& ids, const QString& key, const tr_benc * value )
+Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& value )
{
tr_benc top;
tr_bencInitDict( &top, 2 );
tr_bencDictAddStr( &top, "method", "torrent-set" );
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
addOptionalIds( args, ids );
- tr_benc * child( tr_bencDictAdd( args, key.toUtf8().constData() ) );
- memcpy( child, value, sizeof(tr_benc) );
+ tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
+ foreach( int i, value )
+ tr_bencListAddInt( list, i );
exec( &top );
tr_bencFree( &top );
}
#include <QString>
#include <QUrl>
+class QStringList;
+
#include <libtransmission/transmission.h>
extern "C"
void torrentSet( const QSet<int>& ids, const QString& key, int val );
void torrentSet( const QSet<int>& ids, const QString& key, double val );
void torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& val );
- void torrentSet( const QSet<int>& ids, const QString& key, const tr_benc * value );
+ void torrentSet( const QSet<int>& ids, const QString& key, const QStringList& val );
void torrentSetLocation( const QSet<int>& ids, const QString& path, bool doMove );
int64_t i;
const char * str;
TrackerStat trackerStat;
- if( tr_bencDictFindStr( child, "announce", &str ) )
+ if( tr_bencDictFindStr( child, "announce", &str ) ) {
trackerStat.announce = QString::fromUtf8( str );
+ dynamic_cast<MyApp*>(QApplication::instance())->favicons.add( QUrl( trackerStat.announce ) );
+ }
if( tr_bencDictFindInt( child, "announceState", &i ) )
trackerStat.announceState = i;
if( tr_bencDictFindInt( child, "downloadCount", &i ) )
return s;
}
+
+QPixmap
+TrackerStat :: getFavicon( ) const
+{
+ MyApp * myApp = dynamic_cast<MyApp*>(QApplication::instance());
+ return myApp->favicons.find( QUrl( announce ) );
+}
+
}
class Prefs;
+class QPixmap;
class QStyle;
struct Peer
int scrapeState;
int seederCount;
int tier;
+ QPixmap getFavicon( ) const;
};
typedef QList<TrackerStat> TrackerStatsList;
--- /dev/null
+/*
+ * This file Copyright (C) 2009-2010 Mnemosyne LLC
+ *
+ * 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: torrent-delegate.cc 11051 2010-07-24 23:51:02Z charles $
+ */
+
+#include <iostream>
+
+#include <QApplication>
+#include <QBrush>
+#include <QFont>
+#include <QFontMetrics>
+#include <QIcon>
+#include <QModelIndex>
+#include <QPainter>
+#include <QPixmap>
+#include <QPixmapCache>
+#include <QStyleOptionProgressBarV2>
+#include <QTextDocument>
+#include <QUrl>
+
+#include "favicon.h"
+#include "formatter.h"
+#include "torrent.h"
+#include "tracker-delegate.h"
+#include "tracker-model.h"
+
+/***
+****
+***/
+
+namespace
+{
+ const int mySpacing = 6;
+ const QSize myMargin( 10, 6 );
+}
+
+QSize
+TrackerDelegate :: margin( const QStyle& style ) const
+{
+ Q_UNUSED( style );
+
+ return myMargin;
+}
+
+/***
+****
+***/
+
+QSize
+TrackerDelegate :: sizeHint( const QStyleOptionViewItem& option, const TrackerInfo& info ) const
+{
+ Q_UNUSED( option );
+
+ QPixmap favicon = info.st.getFavicon( );
+
+ const QString text = TrackerDelegate :: getText( info );
+ QTextDocument textDoc;
+ textDoc.setHtml( text );
+ const QSize textSize = textDoc.size().toSize();
+
+ return QSize( myMargin.width() + favicon.width() + mySpacing + textSize.width() + myMargin.width(),
+ myMargin.height() + qMax<int>( favicon.height(), textSize.height() ) + myMargin.height() );
+}
+
+QSize
+TrackerDelegate :: sizeHint( const QStyleOptionViewItem & option,
+ const QModelIndex & index ) const
+{
+ const TrackerInfo trackerInfo = index.model()->data( index, TrackerModel::TrackerRole ).value<TrackerInfo>();
+ return sizeHint( option, trackerInfo );
+}
+
+void
+TrackerDelegate :: paint( QPainter * painter,
+ const QStyleOptionViewItem & option,
+ const QModelIndex & index) const
+{
+ const TrackerInfo trackerInfo = index.model()->data( index, TrackerModel::TrackerRole ).value<TrackerInfo>();
+ painter->save( );
+ painter->setClipRect( option.rect );
+ drawBackground( painter, option, index );
+ drawTracker( painter, option, trackerInfo );
+ drawFocus(painter, option, option.rect );
+ painter->restore( );
+}
+
+void
+TrackerDelegate :: drawTracker( QPainter * painter,
+ const QStyleOptionViewItem & option,
+ const TrackerInfo & inf ) const
+{
+ painter->save( );
+
+ QPixmap icon = inf.st.getFavicon( );
+ QRect iconArea( option.rect.x() + myMargin.width(),
+ option.rect.y() + myMargin.height(),
+ icon.width(),
+ icon.height() );
+ painter->drawPixmap( iconArea.x(), iconArea.y()+4, icon );
+
+ const int textWidth = option.rect.width() - myMargin.width()*2 - mySpacing - icon.width();
+ const int textX = myMargin.width() + icon.width() + mySpacing;
+ const QString text = getText( inf );
+ QTextDocument textDoc;
+ textDoc.setHtml( text );
+ const QRect textRect( textX, iconArea.y(), textWidth, option.rect.height() - myMargin.height()*2 );
+ painter->translate( textRect.topLeft( ) );
+ textDoc.drawContents( painter, textRect.translated( -textRect.topLeft( ) ) );
+
+ painter->restore( );
+}
+
+void
+TrackerDelegate :: setShowMore( bool b )
+{
+ myShowMore = b;
+}
+
+namespace
+{
+ QString timeToStringRounded( int seconds )
+ {
+ if( seconds > 60 ) seconds -= ( seconds % 60 );
+ return Formatter::timeToString ( seconds );
+ }
+}
+
+QString
+TrackerDelegate :: getText( const TrackerInfo& inf ) const
+{
+ QString key;
+ QString str;
+ const time_t now( time( 0 ) );
+ const QString err_markup_begin = "<span style=\"color:red\">";
+ const QString err_markup_end = "</span>";
+ const QString timeout_markup_begin = "<span style=\"color:#224466\">";
+ const QString timeout_markup_end = "</span>";
+ const QString success_markup_begin = "<span style=\"color:#008B00\">";
+ const QString success_markup_end = "</span>";
+
+ // hostname
+ const QString host = Favicons::getHost( QUrl( inf.st.announce ) );
+ str += inf.st.isBackup ? "<i>" : "<b>";
+ str += host;
+ if( !key.isEmpty( ) ) str += " - " + key;
+ str += inf.st.isBackup ? "</i>" : "</b>";
+
+ // announce & scrape info
+ if( !inf.st.isBackup )
+ {
+ if( inf.st.hasAnnounced )
+ {
+ const QString tstr( timeToStringRounded( now - inf.st.lastAnnounceTime ) );
+ str += "<br/>\n";
+ if( inf.st.lastAnnounceSucceeded )
+ {
+ str += tr( "Got a list of %1%2 peers%3 %4 ago" )
+ .arg( success_markup_begin )
+ .arg( inf.st.lastAnnouncePeerCount )
+ .arg( success_markup_end )
+ .arg( tstr );
+ }
+ else if( inf.st.lastAnnounceTimedOut )
+ {
+ str += tr( "Peer list request timed out %1%2%3 ago; will retry" )
+ .arg( timeout_markup_begin )
+ .arg( tstr )
+ .arg( timeout_markup_end );
+ }
+ else
+ {
+ str += tr( "Got an error %1'%2'%3 %4 ago" )
+ .arg( err_markup_begin )
+ .arg( tstr )
+ .arg( err_markup_end )
+ .arg( tstr );
+ }
+ }
+
+ switch( inf.st.announceState )
+ {
+ case TR_TRACKER_INACTIVE:
+ if( inf.st.hasAnnounced ) {
+ str += "<br/>\n";
+ str += tr( "No updates scheduled" );
+ }
+ break;
+
+ case TR_TRACKER_WAITING: {
+ const QString tstr( timeToStringRounded( inf.st.nextAnnounceTime - now ) );
+ str += "<br/>\n";
+ str += tr( "Asking for more peers in %1" ).arg( tstr );
+ break;
+ }
+
+ case TR_TRACKER_QUEUED:
+ str += "<br/>\n";
+ str += tr( "Queued to ask for more peers" );
+ break;
+
+ case TR_TRACKER_ACTIVE: {
+ const QString tstr( timeToStringRounded( now - inf.st.lastAnnounceStartTime ) );
+ str += "<br/>\n";
+ str += tr( "Asking for more peers now... <small>%1</small>" ).arg( tstr );
+ break;
+ }
+ }
+
+ if( myShowMore )
+ {
+ if( inf.st.hasScraped )
+ {
+ str += "<br/>\n";
+ const QString tstr( timeToStringRounded( now - inf.st.lastScrapeTime ) );
+ if( inf.st.lastScrapeSucceeded )
+ {
+ str += tr( "Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago" )
+ .arg( success_markup_begin )
+ .arg( inf.st.seederCount )
+ .arg( success_markup_end )
+ .arg( success_markup_begin )
+ .arg( inf.st.leecherCount )
+ .arg( success_markup_end )
+ .arg( tstr );
+ }
+ else
+ {
+ str += tr( "Got a scrape error %1'%2'%3 %4 ago" )
+ .arg( err_markup_begin )
+ .arg( inf.st.lastScrapeResult )
+ .arg( err_markup_end )
+ .arg( tstr );
+ }
+ }
+
+ switch( inf.st.scrapeState )
+ {
+ case TR_TRACKER_INACTIVE:
+ break;
+
+ case TR_TRACKER_WAITING: {
+ str += "<br/>\n";
+ const QString tstr( timeToStringRounded( inf.st.nextScrapeTime - now ) );
+ str += tr( "Asking for peer counts in %1" ).arg( tstr );
+ break;
+ }
+
+ case TR_TRACKER_QUEUED: {
+ str += "<br/>\n";
+ str += tr( "Queued to ask for peer counts" );
+ break;
+ }
+
+ case TR_TRACKER_ACTIVE: {
+ str += "<br/>\n";
+ const QString tstr( timeToStringRounded( now - inf.st.lastScrapeStartTime ) );
+ str += tr( "Asking for peer counts now... <small>%1</small>" ).arg( tstr );
+ break;
+ }
+ }
+ }
+ }
+
+ return str;
+}
+
+#if 0
+
+ if( inf.isBackup )
+ str += "<i>";
+ QString announce;
+ int announceState;
+ int downloadCount;
+ bool hasAnnounced; bool hasScraped;
+ QString host;
+ int id;
+ bool isBackup;
+ int lastAnnouncePeerCount;
+ int lastAnnounceResult;
+ int lastAnnounceStartTime;
+ bool lastAnnounceSucceeded;
+ int lastAnnounceTime;
+ bool lastAnnounceTimedOut;
+ QString lastScrapeResult;
+ int lastScrapeStartTime;
+ bool lastScrapeSucceeded;
+ int lastScrapeTime;
+ bool lastScrapeTimedOut;
+ int leecherCount;
+ int nextAnnounceTime;
+ int nextScrapeTime;
+ int scrapeState;
+ int seederCount;
+ int tier;
+
+}
+#endif
--- /dev/null
+/*
+ * This file Copyright (C) 2009-2010 Mnemosyne LLC
+ *
+ * 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: torrent-delegate.h 9868 2010-01-04 21:00:47Z charles $
+ */
+
+#ifndef QTR_TORRENT_DELEGATE_H
+#define QTR_TORRENT_DELEGATE_H
+
+#include <QItemDelegate>
+#include <QSize>
+
+class QPainter;
+class QStyleOptionViewItem;
+class QStyle;
+class Session;
+class TrackerInfo;
+
+class TrackerDelegate: public QItemDelegate
+{
+ Q_OBJECT
+
+ public:
+ TrackerDelegate( QObject * parent=0 ): QItemDelegate(parent), myShowMore(false) { }
+ virtual ~TrackerDelegate( ) { }
+
+ public:
+ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
+ public:
+ void setShowMore( bool b );
+
+ protected:
+ QString getText( const TrackerInfo& ) const;
+ QSize margin( const QStyle& style ) const;
+ virtual QSize sizeHint( const QStyleOptionViewItem&, const TrackerInfo& ) const;
+ void drawTracker( QPainter*, const QStyleOptionViewItem&, const TrackerInfo& ) const;
+
+ private:
+ bool myShowMore;
+};
+
+#endif
--- /dev/null
+/*
+ * This file Copyright (C) 2010 Mnemosyne LLC
+ *
+ * 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$
+ */
+
+#include <algorithm> // std::sort()
+
+#include <QUrl>
+
+#include "app.h" // MyApp
+#include "tracker-model.h"
+
+int
+TrackerModel :: rowCount( const QModelIndex& parent ) const
+{
+ Q_UNUSED( parent );
+
+ return parent.isValid() ? 0 : myRows.size();
+}
+
+QVariant
+TrackerModel :: data( const QModelIndex& index, int role ) const
+{
+ QVariant var;
+
+ const int row = index.row( );
+ if( ( 0<=row ) && ( row<myRows.size( ) ) )
+ {
+ const TrackerInfo& trackerInfo = myRows.at( row );
+
+ switch( role )
+ {
+ case Qt::DisplayRole:
+ var = QString( trackerInfo.st.announce );
+ break;
+
+ case Qt::DecorationRole:
+ var = trackerInfo.st.getFavicon( );
+ break;
+
+ case TrackerRole:
+ var = qVariantFromValue( trackerInfo );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return var;
+}
+
+/***
+****
+***/
+
+struct CompareTrackers {
+ bool operator()( const TrackerInfo& a, const TrackerInfo& b ) const {
+ if( a.torrentId != b.torrentId ) return a.torrentId < b.torrentId;
+ if( a.st.tier != b.st.tier ) return a.st.tier < b.st.tier;
+ return a.st.announce < b.st.announce;
+ }
+};
+
+void
+TrackerModel :: refresh( const TorrentModel& torrentModel, const QSet<int>& ids )
+{
+ // build a list of the TrackerInfos
+ QVector<TrackerInfo> trackers;
+ foreach( int id, ids ) {
+ const Torrent * tor = torrentModel.getTorrentFromId( id );
+ if( tor != 0 ) {
+ const TrackerStatsList trackerList = tor->trackerStats( );
+ foreach( const TrackerStat& st, trackerList ) {
+ TrackerInfo trackerInfo;
+ trackerInfo.st = st;
+ trackerInfo.torrentId = id;
+ trackers.append( trackerInfo );
+ }
+ }
+ }
+
+ // sort 'em
+ CompareTrackers comp;
+ std::sort( trackers.begin(), trackers.end(), comp );
+
+ // merge 'em with the existing list
+ int old_index = 0;
+ int new_index = 0;
+
+ while( ( old_index < myRows.size() ) || ( new_index < trackers.size() ) )
+ {
+ if( old_index == myRows.size() )
+ {
+ // add this new row
+ beginInsertRows( QModelIndex( ), old_index, old_index );
+ myRows.insert( old_index, trackers.at( new_index ) );
+ endInsertRows( );
+ ++old_index;
+ ++new_index;
+ }
+ else if( new_index == trackers.size() )
+ {
+ // remove this old row
+ beginRemoveRows( QModelIndex( ), old_index, old_index );
+ myRows.remove( old_index );
+ endRemoveRows( );
+ }
+ else if( comp( myRows.at(old_index), trackers.at(new_index) ) )
+ {
+ // remove this old row
+ beginRemoveRows( QModelIndex( ), old_index, old_index );
+ myRows.remove( old_index );
+ endRemoveRows( );
+ }
+ else if( comp( trackers.at(new_index), myRows.at(old_index) ) )
+ {
+ // add this new row
+ beginInsertRows( QModelIndex( ), old_index, old_index );
+ myRows.insert( old_index, trackers.at( new_index ) );
+ endInsertRows( );
+ ++old_index;
+ ++new_index;
+ }
+ else // update existing row
+ {
+ myRows[old_index].st = trackers.at(new_index).st;
+ QModelIndex topLeft;
+ QModelIndex bottomRight;
+ dataChanged( index(old_index,0), index(old_index,0) );
+ ++old_index;
+ ++new_index;
+ }
+ }
+}
+
+int
+TrackerModel :: find( int torrentId, const QString& url ) const
+{
+ for( int i=0, n=myRows.size(); i<n; ++i ) {
+ const TrackerInfo& inf = myRows.at(i);
+ if( ( inf.torrentId == torrentId ) && ( url == inf.st.announce ) )
+ return i;
+ }
+
+ return -1;
+}
--- /dev/null
+/*
+ * This file Copyright (C) 2010 Mnemosyne LLC
+ *
+ * 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$
+ */
+
+#ifndef QTR_TRACKER_MODEL_H
+#define QTR_TRACKER_MODEL_H
+
+#include <QAbstractListModel>
+#include <QSet>
+#include <QVector>
+
+#include "torrent.h"
+#include "torrent-model.h"
+
+struct TrackerInfo
+{
+ TrackerStat st;
+ int torrentId;
+};
+Q_DECLARE_METATYPE(TrackerInfo)
+
+class TrackerModel: public QAbstractListModel
+{
+ Q_OBJECT
+
+ typedef QVector<TrackerInfo> rows_t;
+ rows_t myRows;
+
+ public:
+ void refresh( const TorrentModel&, const QSet<int>& ids );
+ int find( int torrentId, const QString& url ) const;
+
+ public:
+ virtual int rowCount( const QModelIndex& parent = QModelIndex() ) const;
+ virtual QVariant data( const QModelIndex& index, int role = Qt::DisplayRole ) const;
+ enum Role { TrackerRole = Qt::UserRole };
+
+ public:
+ TrackerModel( ) { }
+ virtual ~TrackerModel( ) { }
+};
+
+#endif
<context>
<name>Details</name>
<message>
- <location filename="details.cc" line="145"/>
+ <location filename="details.cc" line="151"/>
<source>Torrent Properties</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="149"/>
+ <location filename="details.cc" line="155"/>
<source>Information</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="151"/>
+ <location filename="details.cc" line="157"/>
<source>Peers</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="153"/>
+ <location filename="details.cc" line="159"/>
<source>Tracker</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="155"/>
+ <location filename="details.cc" line="161"/>
<source>Files</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="157"/>
+ <location filename="details.cc" line="163"/>
<source>Options</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="256"/>
+ <location filename="details.cc" line="287"/>
<source>None</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="257"/>
+ <location filename="details.cc" line="288"/>
<source>Mixed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="258"/>
- <location filename="details.cc" line="441"/>
+ <location filename="details.cc" line="289"/>
+ <location filename="details.cc" line="472"/>
<source>Unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="290"/>
+ <location filename="details.cc" line="321"/>
<source>Finished</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="292"/>
+ <location filename="details.cc" line="323"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="328"/>
+ <location filename="details.cc" line="359"/>
<source>%1 (%2%)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="332"/>
+ <location filename="details.cc" line="363"/>
<source>%1 (%2%); %3 Unverified</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="363"/>
+ <location filename="details.cc" line="394"/>
<source>%1 (+%2 corrupt)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="461"/>
+ <location filename="details.cc" line="492"/>
<source>Active now</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="463"/>
+ <location filename="details.cc" line="494"/>
<source>%1 ago</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location filename="details.cc" line="504"/>
+ <location filename="details.cc" line="535"/>
<source>%1 (%Ln pieces @ %2)</source>
<translation>
<numerusform>%1 (%Ln piece @ %2)</numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="details.cc" line="508"/>
+ <location filename="details.cc" line="539"/>
<source>%1 (%Ln pieces)</source>
<translation>
<numerusform>%1 (%Ln piece)</numerusform>
</translation>
</message>
<message>
- <location filename="details.cc" line="532"/>
+ <location filename="details.cc" line="563"/>
<source>Private to this tracker -- DHT and PEX disabled</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="533"/>
+ <location filename="details.cc" line="564"/>
<source>Public torrent</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="572"/>
+ <location filename="details.cc" line="603"/>
<source>Created by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="574"/>
+ <location filename="details.cc" line="605"/>
<source>Created on %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="576"/>
+ <location filename="details.cc" line="607"/>
<source>Created by %1 on %2</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="735"/>
+ <location filename="details.cc" line="778"/>
<source>Got a list of %1 peers %2 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="741"/>
+ <location filename="details.cc" line="784"/>
<source>Peer list request timed out %1 ago; will retry</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="746"/>
+ <location filename="details.cc" line="789"/>
<source>Got an error %1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="756"/>
+ <location filename="details.cc" line="799"/>
<source>No updates scheduled</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="763"/>
+ <location filename="details.cc" line="806"/>
<source>Asking for more peers in %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="769"/>
+ <location filename="details.cc" line="812"/>
<source>Queued to ask for more peers</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="775"/>
+ <location filename="details.cc" line="818"/>
<source>Asking for more peers now... %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="788"/>
+ <location filename="details.cc" line="831"/>
<source>Tracker had %1 seeders and %2 leechers %3 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="795"/>
+ <location filename="details.cc" line="838"/>
<source>Got a scrape error %1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="807"/>
+ <location filename="details.cc" line="850"/>
<source>Asking for peer counts in %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="813"/>
+ <location filename="details.cc" line="856"/>
<source>Queued to ask for peer counts</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="819"/>
+ <location filename="details.cc" line="862"/>
<source>Asking for peer counts now... %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="883"/>
- <location filename="details.cc" line="904"/>
+ <location filename="details.cc" line="926"/>
+ <location filename="details.cc" line="947"/>
<source>Encrypted connection</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="897"/>
+ <location filename="details.cc" line="940"/>
<source>Optimistic unchoke</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="898"/>
+ <location filename="details.cc" line="941"/>
<source>Downloading from this peer</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="899"/>
+ <location filename="details.cc" line="942"/>
<source>We would download from this peer if they would let us</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="900"/>
+ <location filename="details.cc" line="943"/>
<source>Uploading to peer</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="901"/>
+ <location filename="details.cc" line="944"/>
<source>We would upload to this peer if they asked</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="902"/>
+ <location filename="details.cc" line="945"/>
<source>Peer has unchoked us, but we're not interested</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="903"/>
+ <location filename="details.cc" line="946"/>
<source>We unchoked this peer, but they're not interested</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="905"/>
+ <location filename="details.cc" line="948"/>
<source>Peer was discovered through DHT</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="906"/>
+ <location filename="details.cc" line="949"/>
<source>Peer was discovered through Peer Exchange (PEX)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="907"/>
+ <location filename="details.cc" line="950"/>
<source>Peer is an incoming connection</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="963"/>
+ <location filename="details.cc" line="1006"/>
<source>Activity</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="964"/>
+ <location filename="details.cc" line="1007"/>
<source>Torrent size:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="965"/>
+ <location filename="details.cc" line="1008"/>
<source>Have:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="966"/>
+ <location filename="details.cc" line="1009"/>
<source>Availability:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="967"/>
+ <location filename="details.cc" line="1010"/>
<source>Downloaded:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="968"/>
+ <location filename="details.cc" line="1011"/>
<source>Uploaded:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="969"/>
+ <location filename="details.cc" line="1012"/>
<source>Ratio:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="970"/>
+ <location filename="details.cc" line="1013"/>
<source>State:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="971"/>
+ <location filename="details.cc" line="1014"/>
<source>Running time:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="972"/>
+ <location filename="details.cc" line="1015"/>
<source>Remaining time:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="973"/>
+ <location filename="details.cc" line="1016"/>
<source>Last activity:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="974"/>
+ <location filename="details.cc" line="1017"/>
<source>Error:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="978"/>
+ <location filename="details.cc" line="1021"/>
<source>Details</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="979"/>
+ <location filename="details.cc" line="1022"/>
<source>Location:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="980"/>
+ <location filename="details.cc" line="1023"/>
<source>Hash:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="981"/>
+ <location filename="details.cc" line="1024"/>
<source>Privacy:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="982"/>
+ <location filename="details.cc" line="1025"/>
<source>Origin:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="984"/>
+ <location filename="details.cc" line="1027"/>
<source>Comment:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1100"/>
+ <location filename="details.cc" line="1125"/>
<source>Add tracker announce URL </source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1124"/>
+ <location filename="details.cc" line="1137"/>
+ <location filename="details.cc" line="1165"/>
+ <source>Error</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1137"/>
+ <source>Tracker already exists.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1158"/>
<source>Edit tracker announce URL </source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1196"/>
+ <location filename="details.cc" line="1165"/>
+ <source>Invalid URL "%1"</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1213"/>
<source>Speed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1198"/>
+ <location filename="details.cc" line="1215"/>
<source>Honor global &limits</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1203"/>
+ <location filename="details.cc" line="1220"/>
<source>Limit &download speed (%1):</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1213"/>
+ <location filename="details.cc" line="1230"/>
<source>Limit &upload speed (%1):</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1224"/>
+ <location filename="details.cc" line="1241"/>
<source>High</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1225"/>
+ <location filename="details.cc" line="1242"/>
<source>Normal</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1226"/>
+ <location filename="details.cc" line="1243"/>
<source>Low</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1228"/>
+ <location filename="details.cc" line="1245"/>
<source>Torrent &priority:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1232"/>
- <source>Seed-Until Ratio</source>
+ <location filename="details.cc" line="1249"/>
+ <source>Seeding Limits</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1234"/>
- <source>Use &global settings</source>
+ <location filename="details.cc" line="1254"/>
+ <location filename="details.cc" line="1268"/>
+ <source>Use Global Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1240"/>
- <source>Seed &regardless of ratio</source>
+ <location filename="details.cc" line="1255"/>
+ <source>Seed regardless of ratio</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1248"/>
- <source>&Seed torrent until its ratio reaches:</source>
+ <location filename="details.cc" line="1256"/>
+ <source>Stop seeding at ratio:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1261"/>
- <source>Peer Connections</source>
+ <location filename="details.cc" line="1263"/>
+ <source>&Ratio:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1267"/>
- <source>&Maximum peers:</source>
+ <location filename="details.cc" line="1269"/>
+ <source>Seed regardless of activity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1270"/>
+ <source>Stop seeding if idle for N minutes:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1297"/>
- <source>Trackers</source>
+ <location filename="details.cc" line="1277"/>
+ <source>&Idle:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1340"/>
+ <location filename="details.cc" line="1281"/>
+ <source>Peer Connections</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1287"/>
+ <source>&Maximum peers:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1359"/>
<source>Show &more details</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Up</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Down</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>%</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Address</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Client</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="formatter.cc" line="38"/>
<source>B/s</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="39"/>
<source>kB/s</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="40"/>
<source>MB/s</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="41"/>
<source>GB/s</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="42"/>
<source>TB/s</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="50"/>
<location filename="formatter.cc" line="62"/>
<source>B</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="51"/>
<source>KB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="52"/>
<source>MB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="53"/>
<source>GB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="54"/>
<source>TB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="63"/>
<source>KiB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="64"/>
<source>MiB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="65"/>
<source>GiB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="66"/>
<source>TiB</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message>
<location filename="formatter.cc" line="98"/>
<location filename="formatter.cc" line="110"/>
<location filename="formatter.cc" line="122"/>
<source>None</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
<message numerus="yes">
<location filename="formatter.cc" line="159"/>
<source>%Ln day(s)</source>
- <translation type="unfinished">
+ <translation>
<numerusform>%Ln day</numerusform>
<numerusform>%Ln days</numerusform>
</translation>
<message numerus="yes">
<location filename="formatter.cc" line="160"/>
<source>%Ln hour(s)</source>
- <translation type="unfinished">
+ <translation>
<numerusform>%Ln hour</numerusform>
<numerusform>%Ln hours</numerusform>
</translation>
<message numerus="yes">
<location filename="formatter.cc" line="161"/>
<source>%Ln minute(s)</source>
- <translation type="unfinished">
+ <translation>
<numerusform>%Ln minute</numerusform>
<numerusform>%Ln minutes</numerusform>
</translation>
<message numerus="yes">
<location filename="formatter.cc" line="162"/>
<source>%Ln second(s)</source>
- <translation type="unfinished">
+ <translation>
<numerusform>%Ln second</numerusform>
<numerusform>%Ln seconds</numerusform>
</translation>
<location filename="formatter.cc" line="176"/>
<location filename="formatter.cc" line="183"/>
<source>%1, %2</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
</context>
<context>
<message>
<location filename="license.cc" line="22"/>
<source>License</source>
- <translation type="unfinished"></translation>
+ <translation></translation>
</message>
</context>
<context>
</message>
<message>
<location filename="mainwin.ui" line="336"/>
- <source>Alt+M</source>
+ <source>Alt+C</source>
+ <oldsource>Alt+M</oldsource>
<translation type="unfinished"></translation>
</message>
<message>
</message>
<message>
<location filename="prefs-dialog.cc" line="373"/>
- <location filename="prefs-dialog.cc" line="730"/>
+ <location filename="prefs-dialog.cc" line="735"/>
<source>Status unknown</source>
<translation type="unfinished"></translation>
</message>
<source><b>Update succeeded!</b><p>Blocklist now has %Ln rules.</source>
<translation>
<numerusform></numerusform>
- <numerusform></numerusform>
</translation>
</message>
<message>
</message>
<message>
<location filename="prefs-dialog.cc" line="475"/>
- <location filename="prefs-dialog.cc" line="625"/>
+ <location filename="prefs-dialog.cc" line="630"/>
<source>Privacy</source>
<translation type="unfinished"></translation>
</message>
</message>
<message>
<location filename="prefs-dialog.cc" line="598"/>
- <source>Seeding</source>
+ <source>Seeding Limits</source>
+ <oldsource>Seeding</oldsource>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="prefs-dialog.cc" line="600"/>
- <source>&Seed torrent until its ratio reaches:</source>
+ <source>Stop seeding at &ratio:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="prefs-dialog.cc" line="605"/>
+ <source>Stop seeding if idle for &N minutes:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="620"/>
+ <location filename="prefs-dialog.cc" line="625"/>
<source>Transmission Preferences</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="623"/>
+ <location filename="prefs-dialog.cc" line="628"/>
<source>Torrents</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="624"/>
+ <location filename="prefs-dialog.cc" line="629"/>
<source>Speed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="626"/>
+ <location filename="prefs-dialog.cc" line="631"/>
<source>Network</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="627"/>
+ <location filename="prefs-dialog.cc" line="632"/>
<source>Web</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="656"/>
+ <location filename="prefs-dialog.cc" line="661"/>
<source>Not supported by remote sessions</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="682"/>
+ <location filename="prefs-dialog.cc" line="687"/>
<source>Enable &blocklist</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location filename="prefs-dialog.cc" line="684"/>
+ <location filename="prefs-dialog.cc" line="689"/>
<source>Enable &blocklist (%Ln rules)</source>
<translation>
<numerusform>Enable &blocklist (%Ln rule)</numerusform>
<context>
<name>Session</name>
<message>
- <location filename="session.cc" line="765"/>
+ <location filename="session.cc" line="769"/>
<source>Add Torrent</source>
<translation type="unfinished"></translation>
</message>
<context>
<name>Torrent</name>
<message>
- <location filename="torrent.cc" line="677"/>
+ <location filename="torrent.cc" line="683"/>
<source>Waiting to verify local data</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="678"/>
+ <location filename="torrent.cc" line="684"/>
<source>Verifying local data</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="679"/>
+ <location filename="torrent.cc" line="685"/>
<source>Downloading</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="680"/>
+ <location filename="torrent.cc" line="686"/>
<source>Seeding</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="681"/>
+ <location filename="torrent.cc" line="687"/>
<source>Finished</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="681"/>
+ <location filename="torrent.cc" line="687"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="694"/>
+ <location filename="torrent.cc" line="700"/>
<source>Tracker gave a warning: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="695"/>
+ <location filename="torrent.cc" line="701"/>
<source>Tracker gave an error: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="696"/>
+ <location filename="torrent.cc" line="702"/>
<source>Error: %1</source>
<translation type="unfinished"></translation>
</message>
</message>
<message>
<location filename="torrent-delegate.cc" line="150"/>
- <location filename="torrent-delegate.cc" line="250"/>
+ <location filename="torrent-delegate.cc" line="252"/>
<source> - </source>
<translation type="unfinished"></translation>
</message>
<source>Remaining time unknown</source>
<translation type="unfinished"></translation>
</message>
- <message>
- <location filename="torrent-delegate.cc" line="174"/>
- <source>Down: %1, Up: %2</source>
- <translation type="unfinished"></translation>
- </message>
<message>
<location filename="torrent-delegate.cc" line="176"/>
- <source>Down: %1</source>
+ <source>%1 %2, %3 %4</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="torrent-delegate.cc" line="178"/>
- <source>Up: %1</source>
+ <location filename="torrent-delegate.cc" line="180"/>
+ <source>%1 %2</source>
+ <oldsource>Up: %1</oldsource>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="180"/>
+ <location filename="torrent-delegate.cc" line="182"/>
<source>Idle</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="193"/>
+ <location filename="torrent-delegate.cc" line="195"/>
<source>Verifying local data (%1% tested)</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="199"/>
+ <location filename="torrent-delegate.cc" line="201"/>
<source>Ratio: %1, </source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="230"/>
+ <location filename="torrent-delegate.cc" line="232"/>
<source>Downloading from %1 of %n connected peer(s)</source>
<translation>
<numerusform>Downloading from %1 peer</numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="233"/>
+ <location filename="torrent-delegate.cc" line="235"/>
<source>Downloading metadata from %n peer(s) (%1% done)</source>
<translation type="unfinished">
<numerusform></numerusform>
- <numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="238"/>
+ <location filename="torrent-delegate.cc" line="240"/>
<source>Seeding to %1 of %n connected peer(s)</source>
<translation>
<numerusform>Seeding to %1 peer</numerusform>
</translation>
</message>
</context>
+<context>
+ <name>TrackerDelegate</name>
+ <message>
+ <location filename="tracker-delegate.cc" line="161"/>
+ <source>Got a list of %1%2 peers%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="169"/>
+ <source>Peer list request timed out %1%2%3 ago; will retry</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="176"/>
+ <source>Got an error %1'%2'%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="189"/>
+ <source>No updates scheduled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="196"/>
+ <source>Asking for more peers in %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="202"/>
+ <source>Queued to ask for more peers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="208"/>
+ <source>Asking for more peers now... <small>%1</small></source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="221"/>
+ <source>Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="232"/>
+ <source>Got a scrape error %1'%2'%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="248"/>
+ <source>Asking for peer counts in %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="254"/>
+ <source>Queued to ask for peer counts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="261"/>
+ <source>Asking for peer counts now... <small>%1</small></source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>Utils</name>
<message numerus="yes">
<context>
<name>Details</name>
<message>
- <location filename="details.cc" line="145"/>
+ <location filename="details.cc" line="151"/>
<source>Torrent Properties</source>
<translation>Свойства торрента</translation>
</message>
<message>
- <location filename="details.cc" line="149"/>
+ <location filename="details.cc" line="155"/>
<source>Information</source>
<translation>Сведения</translation>
</message>
<message>
- <location filename="details.cc" line="151"/>
+ <location filename="details.cc" line="157"/>
<source>Peers</source>
<translation>Узлы</translation>
</message>
<message>
- <location filename="details.cc" line="153"/>
+ <location filename="details.cc" line="159"/>
<source>Tracker</source>
<translation>Трекер</translation>
</message>
<message>
- <location filename="details.cc" line="155"/>
+ <location filename="details.cc" line="161"/>
<source>Files</source>
<translation>Файлы</translation>
</message>
<message>
- <location filename="details.cc" line="157"/>
+ <location filename="details.cc" line="163"/>
<source>Options</source>
<translation>Параметры</translation>
</message>
<message>
- <location filename="details.cc" line="256"/>
+ <location filename="details.cc" line="287"/>
<source>None</source>
<translation>Н/Д</translation>
</message>
<message>
- <location filename="details.cc" line="257"/>
+ <location filename="details.cc" line="288"/>
<source>Mixed</source>
<translation>Смешанный</translation>
</message>
<message>
- <location filename="details.cc" line="258"/>
- <location filename="details.cc" line="441"/>
+ <location filename="details.cc" line="289"/>
+ <location filename="details.cc" line="472"/>
<source>Unknown</source>
<translation>Неизвестно</translation>
</message>
<message>
- <location filename="details.cc" line="290"/>
+ <location filename="details.cc" line="321"/>
<source>Finished</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="292"/>
+ <location filename="details.cc" line="323"/>
<source>Paused</source>
<translation type="unfinished">Приостановлен</translation>
</message>
<message>
- <location filename="details.cc" line="328"/>
+ <location filename="details.cc" line="359"/>
<source>%1 (%2%)</source>
<translation></translation>
</message>
<message>
- <location filename="details.cc" line="332"/>
+ <location filename="details.cc" line="363"/>
<source>%1 (%2%); %3 Unverified</source>
<translation>%1 (%2%); %3 непроверен</translation>
</message>
<message>
- <location filename="details.cc" line="363"/>
+ <location filename="details.cc" line="394"/>
<source>%1 (+%2 corrupt)</source>
<translation>%1 (+%2 испорчен)</translation>
</message>
<translation type="obsolete">Остановлен</translation>
</message>
<message>
- <location filename="details.cc" line="461"/>
+ <location filename="details.cc" line="492"/>
<source>Active now</source>
<translation>Активизирован</translation>
</message>
<message>
- <location filename="details.cc" line="463"/>
+ <location filename="details.cc" line="494"/>
<source>%1 ago</source>
<translation>%1 назад</translation>
</message>
<message numerus="yes">
- <location filename="details.cc" line="504"/>
+ <location filename="details.cc" line="535"/>
<source>%1 (%Ln pieces @ %2)</source>
<translation>
<numerusform>%1 (%Ln часть @ %2)</numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="details.cc" line="508"/>
+ <location filename="details.cc" line="539"/>
<source>%1 (%Ln pieces)</source>
<translation>
<numerusform>%1 (%Ln часть)</numerusform>
</translation>
</message>
<message>
- <location filename="details.cc" line="532"/>
+ <location filename="details.cc" line="563"/>
<source>Private to this tracker -- DHT and PEX disabled</source>
<translation>Приватно для этого трекера -- DHT и PEX выключены</translation>
</message>
<message>
- <location filename="details.cc" line="533"/>
+ <location filename="details.cc" line="564"/>
<source>Public torrent</source>
<translation>Публичный торрент</translation>
</message>
<message>
- <location filename="details.cc" line="572"/>
+ <location filename="details.cc" line="603"/>
<source>Created by %1</source>
<translation>Создано при помощи %1</translation>
</message>
<message>
- <location filename="details.cc" line="574"/>
+ <location filename="details.cc" line="605"/>
<source>Created on %1</source>
<translation>Создано %1</translation>
</message>
<message>
- <location filename="details.cc" line="576"/>
+ <location filename="details.cc" line="607"/>
<source>Created by %1 on %2</source>
<translation>Создано %2 при помощи %1</translation>
</message>
<translation type="obsolete">Сейчас</translation>
</message>
<message>
- <location filename="details.cc" line="735"/>
+ <location filename="details.cc" line="778"/>
<source>Got a list of %1 peers %2 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="741"/>
+ <location filename="details.cc" line="784"/>
<source>Peer list request timed out %1 ago; will retry</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="746"/>
+ <location filename="details.cc" line="789"/>
<source>Got an error %1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="756"/>
+ <location filename="details.cc" line="799"/>
<source>No updates scheduled</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="763"/>
+ <location filename="details.cc" line="806"/>
<source>Asking for more peers in %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="769"/>
+ <location filename="details.cc" line="812"/>
<source>Queued to ask for more peers</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="775"/>
+ <location filename="details.cc" line="818"/>
<source>Asking for more peers now... %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="788"/>
+ <location filename="details.cc" line="831"/>
<source>Tracker had %1 seeders and %2 leechers %3 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="795"/>
+ <location filename="details.cc" line="838"/>
<source>Got a scrape error %1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="807"/>
+ <location filename="details.cc" line="850"/>
<source>Asking for peer counts in %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="813"/>
+ <location filename="details.cc" line="856"/>
<source>Queued to ask for peer counts</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="819"/>
+ <location filename="details.cc" line="862"/>
<source>Asking for peer counts now... %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="883"/>
- <location filename="details.cc" line="904"/>
+ <location filename="details.cc" line="926"/>
+ <location filename="details.cc" line="947"/>
<source>Encrypted connection</source>
<translation>Зашифрованное соединение</translation>
</message>
<message>
- <location filename="details.cc" line="897"/>
+ <location filename="details.cc" line="940"/>
<source>Optimistic unchoke</source>
<translation>Благоприятная передача</translation>
</message>
<message>
- <location filename="details.cc" line="898"/>
+ <location filename="details.cc" line="941"/>
<source>Downloading from this peer</source>
<translation>Загрузка с этого узла</translation>
</message>
<message>
- <location filename="details.cc" line="899"/>
+ <location filename="details.cc" line="942"/>
<source>We would download from this peer if they would let us</source>
<translation>Возможен приём данных от этого узла, если он позволит</translation>
</message>
<message>
- <location filename="details.cc" line="900"/>
+ <location filename="details.cc" line="943"/>
<source>Uploading to peer</source>
<translation>Передача узлу</translation>
</message>
<message>
- <location filename="details.cc" line="901"/>
+ <location filename="details.cc" line="944"/>
<source>We would upload to this peer if they asked</source>
<translation>Возможна раздача данных этому узлу, если он будет заинтересован</translation>
</message>
<message>
- <location filename="details.cc" line="902"/>
+ <location filename="details.cc" line="945"/>
<source>Peer has unchoked us, but we're not interested</source>
<translation>Узел согласен передавать данные, но мы не заинтересованы</translation>
</message>
<message>
- <location filename="details.cc" line="903"/>
+ <location filename="details.cc" line="946"/>
<source>We unchoked this peer, but they're not interested</source>
<translation>Передача узлу была разрешена, но он не заинтересован</translation>
</message>
<message>
- <location filename="details.cc" line="905"/>
+ <location filename="details.cc" line="948"/>
<source>Peer was discovered through DHT</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="906"/>
+ <location filename="details.cc" line="949"/>
<source>Peer was discovered through Peer Exchange (PEX)</source>
<translation>Узел был обнаружен с помощью обмена узлами (PEX)</translation>
</message>
<message>
- <location filename="details.cc" line="907"/>
+ <location filename="details.cc" line="950"/>
<source>Peer is an incoming connection</source>
<translation>Узел работает в режиме приёма</translation>
</message>
<message>
- <location filename="details.cc" line="963"/>
+ <location filename="details.cc" line="1006"/>
<source>Activity</source>
<translation>Активность</translation>
</message>
<message>
- <location filename="details.cc" line="964"/>
+ <location filename="details.cc" line="1007"/>
<source>Torrent size:</source>
<translation>Размер торрента:</translation>
</message>
<message>
- <location filename="details.cc" line="965"/>
+ <location filename="details.cc" line="1008"/>
<source>Have:</source>
<translation>В наличии:</translation>
</message>
<message>
- <location filename="details.cc" line="966"/>
+ <location filename="details.cc" line="1009"/>
<source>Availability:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="967"/>
+ <location filename="details.cc" line="1010"/>
<source>Downloaded:</source>
<translation>Загружено:</translation>
</message>
<message>
- <location filename="details.cc" line="968"/>
+ <location filename="details.cc" line="1011"/>
<source>Uploaded:</source>
<translation>Роздано:</translation>
</message>
<message>
- <location filename="details.cc" line="969"/>
+ <location filename="details.cc" line="1012"/>
<source>Ratio:</source>
<translation>Рейтинг:</translation>
</message>
<message>
- <location filename="details.cc" line="970"/>
+ <location filename="details.cc" line="1013"/>
<source>State:</source>
<translation>Состояние:</translation>
</message>
<message>
- <location filename="details.cc" line="971"/>
+ <location filename="details.cc" line="1014"/>
<source>Running time:</source>
<translation>Длительность:</translation>
</message>
<message>
- <location filename="details.cc" line="972"/>
+ <location filename="details.cc" line="1015"/>
<source>Remaining time:</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="973"/>
+ <location filename="details.cc" line="1016"/>
<source>Last activity:</source>
<translation>Последняя активность:</translation>
</message>
<message>
- <location filename="details.cc" line="974"/>
+ <location filename="details.cc" line="1017"/>
<source>Error:</source>
<translation>Ошибка:</translation>
</message>
<message>
- <location filename="details.cc" line="978"/>
+ <location filename="details.cc" line="1021"/>
<source>Details</source>
<translation>Подробности</translation>
</message>
<message>
- <location filename="details.cc" line="979"/>
+ <location filename="details.cc" line="1022"/>
<source>Location:</source>
<translation>Местонахождение:</translation>
</message>
<message>
- <location filename="details.cc" line="980"/>
+ <location filename="details.cc" line="1023"/>
<source>Hash:</source>
<translation>Хеш:</translation>
</message>
<message>
- <location filename="details.cc" line="981"/>
+ <location filename="details.cc" line="1024"/>
<source>Privacy:</source>
<translation>Конфиденциальность:</translation>
</message>
<message>
- <location filename="details.cc" line="982"/>
+ <location filename="details.cc" line="1025"/>
<source>Origin:</source>
<translation>Происхождение:</translation>
</message>
<message>
- <location filename="details.cc" line="984"/>
+ <location filename="details.cc" line="1027"/>
<source>Comment:</source>
<translation>Комментарий:</translation>
</message>
<message>
- <location filename="details.cc" line="1100"/>
+ <location filename="details.cc" line="1125"/>
<source>Add tracker announce URL </source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1124"/>
+ <location filename="details.cc" line="1137"/>
+ <location filename="details.cc" line="1165"/>
+ <source>Error</source>
+ <translation type="unfinished">ошибок</translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1137"/>
+ <source>Tracker already exists.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1158"/>
<source>Edit tracker announce URL </source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1196"/>
+ <location filename="details.cc" line="1165"/>
+ <source>Invalid URL "%1"</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1213"/>
<source>Speed</source>
<translation>Скорость</translation>
</message>
<message>
- <location filename="details.cc" line="1198"/>
+ <location filename="details.cc" line="1215"/>
<source>Honor global &limits</source>
<translation>Использовать &глобальные ограничения</translation>
</message>
<message>
- <location filename="details.cc" line="1203"/>
+ <location filename="details.cc" line="1220"/>
<source>Limit &download speed (%1):</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1213"/>
+ <location filename="details.cc" line="1230"/>
<source>Limit &upload speed (%1):</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="details.cc" line="1297"/>
+ <location filename="details.cc" line="1249"/>
+ <source>Seeding Limits</source>
+ <translation type="unfinished">Раздача</translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1254"/>
+ <location filename="details.cc" line="1268"/>
+ <source>Use Global Settings</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1255"/>
+ <source>Seed regardless of ratio</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1256"/>
+ <source>Stop seeding at ratio:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1263"/>
+ <source>&Ratio:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1269"/>
+ <source>Seed regardless of activity</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1270"/>
+ <source>Stop seeding if idle for N minutes:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="details.cc" line="1277"/>
+ <source>&Idle:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
<source>Trackers</source>
- <translation type="unfinished">Трекеры</translation>
+ <translation type="obsolete">Трекеры</translation>
</message>
<message>
- <location filename="details.cc" line="1340"/>
+ <location filename="details.cc" line="1359"/>
<source>Show &more details</source>
<translation type="unfinished"></translation>
</message>
<translation type="obsolete">Ограничить скорость &раздачи (КБ/с):</translation>
</message>
<message>
- <location filename="details.cc" line="1224"/>
+ <location filename="details.cc" line="1241"/>
<source>High</source>
<translation>Высокий</translation>
</message>
<message>
- <location filename="details.cc" line="1225"/>
+ <location filename="details.cc" line="1242"/>
<source>Normal</source>
<translation>Обычный</translation>
</message>
<message>
- <location filename="details.cc" line="1226"/>
+ <location filename="details.cc" line="1243"/>
<source>Low</source>
<translation>Низкий</translation>
</message>
<message>
- <location filename="details.cc" line="1228"/>
+ <location filename="details.cc" line="1245"/>
<source>Torrent &priority:</source>
<translation>&Приоритет торрента:</translation>
</message>
<message>
- <location filename="details.cc" line="1232"/>
<source>Seed-Until Ratio</source>
- <translation>Рейтинг для завершения раздачи</translation>
+ <translation type="obsolete">Рейтинг для завершения раздачи</translation>
</message>
<message>
- <location filename="details.cc" line="1234"/>
<source>Use &global settings</source>
- <translation>Использовать &глобальные настройки</translation>
+ <translation type="obsolete">Использовать &глобальные настройки</translation>
</message>
<message>
- <location filename="details.cc" line="1240"/>
<source>Seed &regardless of ratio</source>
- <translation>Раздавать &несмотря на рейтинг</translation>
+ <translation type="obsolete">Раздавать &несмотря на рейтинг</translation>
</message>
<message>
- <location filename="details.cc" line="1248"/>
<source>&Seed torrent until its ratio reaches:</source>
- <translation>&Раздавать до достижения рейтинга:</translation>
+ <translation type="obsolete">&Раздавать до достижения рейтинга:</translation>
</message>
<message>
- <location filename="details.cc" line="1261"/>
+ <location filename="details.cc" line="1281"/>
<source>Peer Connections</source>
<translation>Соединения с узлами</translation>
</message>
<message>
- <location filename="details.cc" line="1267"/>
+ <location filename="details.cc" line="1287"/>
<source>&Maximum peers:</source>
<translation>&Максимальное количество узлов:</translation>
</message>
<translation type="obsolete">Запрос дополнительных узлов можно будет сделать через:</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Up</source>
<translation>Раздача</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Down</source>
<translation>Приём</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>%</source>
<translation>%</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Status</source>
<translation>Состояние</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Address</source>
<translation>Адрес</translation>
</message>
<message>
- <location filename="details.cc" line="1362"/>
+ <location filename="details.cc" line="1381"/>
<source>Client</source>
<translation>Клиент</translation>
</message>
</message>
<message>
<location filename="mainwin.ui" line="336"/>
- <source>Alt+M</source>
- <translation></translation>
+ <source>Alt+C</source>
+ <oldsource>Alt+M</oldsource>
+ <translation type="unfinished"></translation>
</message>
<message>
<location filename="mainwin.ui" line="344"/>
</message>
<message>
<location filename="prefs-dialog.cc" line="373"/>
- <location filename="prefs-dialog.cc" line="730"/>
+ <location filename="prefs-dialog.cc" line="735"/>
<source>Status unknown</source>
<translation>Статус неизвестен</translation>
</message>
</message>
<message>
<location filename="prefs-dialog.cc" line="475"/>
- <location filename="prefs-dialog.cc" line="625"/>
+ <location filename="prefs-dialog.cc" line="630"/>
<source>Privacy</source>
<translation>Конфиденциальность</translation>
</message>
<source>Call scrip&t when torrent is completed</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <location filename="prefs-dialog.cc" line="600"/>
+ <source>Stop seeding at &ratio:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="prefs-dialog.cc" line="605"/>
+ <source>Stop seeding if idle for &N minutes:</source>
+ <translation type="unfinished"></translation>
+ </message>
<message>
<source>Adding Torrents</source>
<translation type="obsolete">Добавление торрентов</translation>
</message>
<message>
<location filename="prefs-dialog.cc" line="598"/>
- <source>Seeding</source>
+ <source>Seeding Limits</source>
+ <oldsource>Seeding</oldsource>
<translation type="unfinished">Раздача</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="600"/>
<source>&Seed torrent until its ratio reaches:</source>
- <translation>&Раздавать до достижения рейтинга:</translation>
+ <translation type="obsolete">&Раздавать до достижения рейтинга:</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="620"/>
+ <location filename="prefs-dialog.cc" line="625"/>
<source>Transmission Preferences</source>
<translation>Параметры Transmission</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="623"/>
+ <location filename="prefs-dialog.cc" line="628"/>
<source>Torrents</source>
<translation>Торренты</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="624"/>
+ <location filename="prefs-dialog.cc" line="629"/>
<source>Speed</source>
<translation>Скорость</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="626"/>
+ <location filename="prefs-dialog.cc" line="631"/>
<source>Network</source>
<translation>Сеть</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="627"/>
+ <location filename="prefs-dialog.cc" line="632"/>
<source>Web</source>
<translation>Веб-интерфейс</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="656"/>
+ <location filename="prefs-dialog.cc" line="661"/>
<source>Not supported by remote sessions</source>
<translation>Не поддерживается удаленными сеансами</translation>
</message>
<message>
- <location filename="prefs-dialog.cc" line="682"/>
+ <location filename="prefs-dialog.cc" line="687"/>
<source>Enable &blocklist</source>
<translation>&Включить черный список</translation>
</message>
<message numerus="yes">
- <location filename="prefs-dialog.cc" line="684"/>
+ <location filename="prefs-dialog.cc" line="689"/>
<source>Enable &blocklist (%Ln rules)</source>
<translation>
<numerusform>Включить &чёрный список (%Ln правило)</numerusform>
<context>
<name>Session</name>
<message>
- <location filename="session.cc" line="765"/>
+ <location filename="session.cc" line="769"/>
<source>Add Torrent</source>
<translation>Добавить торрент</translation>
</message>
<context>
<name>Torrent</name>
<message>
- <location filename="torrent.cc" line="677"/>
+ <location filename="torrent.cc" line="683"/>
<source>Waiting to verify local data</source>
<translation>Ожидается проверка локальных данных</translation>
</message>
<message>
- <location filename="torrent.cc" line="678"/>
+ <location filename="torrent.cc" line="684"/>
<source>Verifying local data</source>
<translation>Проверка локальных данных</translation>
</message>
<message>
- <location filename="torrent.cc" line="679"/>
+ <location filename="torrent.cc" line="685"/>
<source>Downloading</source>
<translation>Приём</translation>
</message>
<message>
- <location filename="torrent.cc" line="680"/>
+ <location filename="torrent.cc" line="686"/>
<source>Seeding</source>
<translation>Раздача</translation>
</message>
<message>
- <location filename="torrent.cc" line="681"/>
+ <location filename="torrent.cc" line="687"/>
<source>Paused</source>
<translation>Приостановлен</translation>
</message>
<message>
- <location filename="torrent.cc" line="681"/>
+ <location filename="torrent.cc" line="687"/>
<source>Finished</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="694"/>
+ <location filename="torrent.cc" line="700"/>
<source>Tracker gave a warning: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="695"/>
+ <location filename="torrent.cc" line="701"/>
<source>Tracker gave an error: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="torrent.cc" line="696"/>
+ <location filename="torrent.cc" line="702"/>
<source>Error: %1</source>
<translation type="unfinished"></translation>
</message>
</message>
<message>
<location filename="torrent-delegate.cc" line="150"/>
- <location filename="torrent-delegate.cc" line="250"/>
+ <location filename="torrent-delegate.cc" line="252"/>
<source> - </source>
<translation> - </translation>
</message>
<translation>Оставшееся время неизвестно</translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="174"/>
+ <location filename="torrent-delegate.cc" line="176"/>
+ <source>%1 %2, %3 %4</source>
+ <translation type="unfinished">%1, %3 %4 {1 %2,?}</translation>
+ </message>
+ <message>
<source>Down: %1, Up: %2</source>
- <translation>Приём: %1, раздача: %2</translation>
+ <translation type="obsolete">Приём: %1, раздача: %2</translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="176"/>
<source>Down: %1</source>
- <translation>Приём: %1</translation>
+ <translation type="obsolete">Приём: %1</translation>
</message>
<message>
<location filename="torrent-delegate.cc" line="178"/>
- <source>Up: %1</source>
- <translation>Раздача: %1</translation>
+ <location filename="torrent-delegate.cc" line="180"/>
+ <source>%1 %2</source>
+ <oldsource>Up: %1</oldsource>
+ <translation type="unfinished">Раздача: %1</translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="180"/>
+ <location filename="torrent-delegate.cc" line="182"/>
<source>Idle</source>
<translation>Нет активности</translation>
</message>
<translation type="obsolete">Ожидается проверка локальных данных</translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="193"/>
+ <location filename="torrent-delegate.cc" line="195"/>
<source>Verifying local data (%1% tested)</source>
<translation>Проверка локальных данных (%1% проверено)</translation>
</message>
<message>
- <location filename="torrent-delegate.cc" line="199"/>
+ <location filename="torrent-delegate.cc" line="201"/>
<source>Ratio: %1, </source>
<translation>Рейтинг: %1</translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="230"/>
+ <location filename="torrent-delegate.cc" line="232"/>
<source>Downloading from %1 of %n connected peer(s)</source>
<translation>
<numerusform>Приём от %1 из %n подключённого узла</numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="233"/>
+ <location filename="torrent-delegate.cc" line="235"/>
<source>Downloading metadata from %n peer(s) (%1% done)</source>
<translation type="unfinished">
<numerusform></numerusform>
</translation>
</message>
<message numerus="yes">
- <location filename="torrent-delegate.cc" line="238"/>
+ <location filename="torrent-delegate.cc" line="240"/>
<source>Seeding to %1 of %n connected peer(s)</source>
<translation>
<numerusform>Раздача к %1 из %n подключённого узла</numerusform>
<translation>Последний ответ от сервера %1 назад</translation>
</message>
</context>
+<context>
+ <name>TrackerDelegate</name>
+ <message>
+ <location filename="tracker-delegate.cc" line="161"/>
+ <source>Got a list of %1%2 peers%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="169"/>
+ <source>Peer list request timed out %1%2%3 ago; will retry</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="176"/>
+ <source>Got an error %1'%2'%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="189"/>
+ <source>No updates scheduled</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="196"/>
+ <source>Asking for more peers in %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="202"/>
+ <source>Queued to ask for more peers</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="208"/>
+ <source>Asking for more peers now... <small>%1</small></source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="221"/>
+ <source>Tracker had %1%2 seeders%3 and %4%5 leechers%6 %7 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="232"/>
+ <source>Got a scrape error %1'%2'%3 %4 ago</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="248"/>
+ <source>Asking for peer counts in %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="254"/>
+ <source>Queued to ask for peer counts</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="tracker-delegate.cc" line="261"/>
+ <source>Asking for peer counts now... <small>%1</small></source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
<context>
<name>Utils</name>
<message>