2
* This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
4
* This file is licensed by the GPL version 2. Works owned by the
5
* Transmission project are granted a special exemption to clause 2(b)
6
* so that the bulk of its code can remain under the MIT license.
7
* This exemption does not extend to derived works not owned by
8
* the Transmission project.
17
#include <QResizeEvent>
18
#include <QFileDialog>
19
#include <QGridLayout>
23
#include <QDialogButtonBox>
24
#include <QPushButton>
28
#include <QVBoxLayout>
30
#include <libtransmission/transmission.h>
31
#include <libtransmission/bencode.h>
32
#include <libtransmission/utils.h> /* mime64 */
34
#include "file-tree.h"
38
#include "qticonloader.h"
46
Options :: Options( Session& session, const Prefs& prefs, const QString& filename, QWidget * parent ):
47
QDialog( parent, Qt::Dialog ),
51
myDestinationButton( 0 ),
54
myVerifyHash( QCryptographicHash::Sha1 )
57
setWindowTitle( tr( "Add Torrent" ) );
58
QFontMetrics fontMetrics( font( ) );
59
QGridLayout * layout = new QGridLayout( this );
62
const int iconSize( style( )->pixelMetric( QStyle :: PM_SmallIconSize ) );
63
QIcon fileIcon = style( )->standardIcon( QStyle::SP_FileIcon );
64
const QPixmap filePixmap = fileIcon.pixmap( iconSize );
67
int width = fontMetrics.size( 0, "This is a pretty long torrent filename indeed.torrent" ).width( );
68
QLabel * l = new QLabel( tr( "&Torrent file:" ) );
69
layout->addWidget( l, row, 0, Qt::AlignLeft );
70
p = myFileButton = new QPushButton;
71
p->setIcon( filePixmap );
72
p->setMinimumWidth( width );
73
p->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
74
p->installEventFilter( this );
76
layout->addWidget( p, row, 1 );
78
connect( p, SIGNAL(clicked(bool)), this, SLOT(onFilenameClicked()));
80
if( session.isLocal( ) )
82
const QIcon folderIcon = QtIconLoader :: icon( "folder", style()->standardIcon( QStyle::SP_DirIcon ) );
83
const QPixmap folderPixmap = folderIcon.pixmap( iconSize );
85
l = new QLabel( tr( "&Destination folder:" ) );
86
layout->addWidget( l, ++row, 0, Qt::AlignLeft );
87
myDestination.setPath( prefs.getString( Prefs :: DOWNLOAD_DIR ) );
88
p = myDestinationButton = new QPushButton;
89
p->setIcon( folderPixmap );
90
p->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
91
p->installEventFilter( this );
92
layout->addWidget( p, row, 1 );
94
connect( p, SIGNAL(clicked(bool)), this, SLOT(onDestinationClicked()));
97
myTree = new FileTreeView;
98
layout->addWidget( myTree, ++row, 0, 1, 2 );
99
if( !session.isLocal( ) )
100
myTree->hideColumn( 1 ); // hide the % done, since we've no way of knowing
102
if( session.isLocal( ) )
104
p = myVerifyButton = new QPushButton( tr( "&Verify Local Data" ) );
105
layout->addWidget( p, ++row, 0, Qt::AlignLeft );
109
c = myStartCheck = new QCheckBox( tr( "&Start when added" ) );
110
c->setChecked( prefs.getBool( Prefs :: START ) );
111
layout->addWidget( c, ++row, 0, 1, 2, Qt::AlignLeft );
113
c = myTrashCheck = new QCheckBox( tr( "&Delete source file" ) );
114
c->setChecked( prefs.getBool( Prefs :: TRASH_ORIGINAL ) );
115
layout->addWidget( c, ++row, 0, 1, 2, Qt::AlignLeft );
117
QDialogButtonBox * b = new QDialogButtonBox( QDialogButtonBox::Ok|QDialogButtonBox::Cancel, Qt::Horizontal, this );
118
connect( b, SIGNAL(rejected()), this, SLOT(deleteLater()) );
119
connect( b, SIGNAL(accepted()), this, SLOT(onAccepted()) );
120
layout->addWidget( b, ++row, 0, 1, 2 );
122
layout->setRowStretch( 2, 2 );
123
layout->setColumnStretch( 1, 2 );
124
layout->setSpacing( HIG :: PAD );
126
connect( myTree, SIGNAL(priorityChanged(const QSet<int>&,int)), this, SLOT(onPriorityChanged(const QSet<int>&,int)));
127
connect( myTree, SIGNAL(wantedChanged(const QSet<int>&,bool)), this, SLOT(onWantedChanged(const QSet<int>&,bool)));
128
if( session.isLocal( ) )
129
connect( myVerifyButton, SIGNAL(clicked(bool)), this, SLOT(onVerify()));
131
connect( &myVerifyTimer, SIGNAL(timeout()), this, SLOT(onTimeout()));
136
Options :: ~Options( )
146
Options :: refreshButton( QPushButton * p, const QString& text, int width )
148
if( width <= 0 ) width = p->width( );
150
QFontMetrics fontMetrics( font( ) );
151
QString str = fontMetrics.elidedText( text, Qt::ElideRight, width );
156
Options :: refreshFileButton( int width )
158
refreshButton( myFileButton, QFileInfo(myFile).baseName(), width );
162
Options :: refreshDestinationButton( int width )
164
if( myDestinationButton != 0 )
165
refreshButton( myDestinationButton, myDestination.absolutePath(), width );
170
Options :: eventFilter( QObject * o, QEvent * event )
172
if( o==myFileButton && event->type() == QEvent::Resize )
174
refreshFileButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
177
if( o==myDestinationButton && event->type() == QEvent::Resize )
179
refreshDestinationButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
190
Options :: clearInfo( )
193
tr_metainfoFree( &myInfo );
204
tr_ctor * ctor = tr_ctorNew( 0 );
205
tr_ctorSetMetainfoFromFile( ctor, myFile.toUtf8().constData() );
206
const int err = tr_torrentParse( ctor, &myInfo );
212
myPriorities.clear( );
217
myPriorities.insert( 0, myInfo.fileCount, TR_PRI_NORMAL );
218
myWanted.insert( 0, myInfo.fileCount, true );
220
for( tr_file_index_t i=0; i<myInfo.fileCount; ++i ) {
223
file.priority = myPriorities[i];
224
file.wanted = myWanted[i];
225
file.size = myInfo.files[i].length;
227
file.filename = QString::fromUtf8( myInfo.files[i].name );
228
myFiles.append( file );
232
myTree->update( myFiles );
236
Options :: onPriorityChanged( const QSet<int>& fileIndices, int priority )
238
foreach( int i, fileIndices )
239
myPriorities[i] = priority;
243
Options :: onWantedChanged( const QSet<int>& fileIndices, bool isWanted )
245
foreach( int i, fileIndices )
246
myWanted[i] = isWanted;
250
Options :: onAccepted( )
252
// rpc spec section 3.4 "adding a torrent"
255
tr_bencInitDict( &top, 3 );
256
tr_bencDictAddStr( &top, "method", "torrent-add" );
257
tr_bencDictAddInt( &top, "tag", Session::ADD_TORRENT_TAG );
258
tr_benc * args( tr_bencDictAddDict( &top, "arguments", 10 ) );
261
if( myDestinationButton )
262
tr_bencDictAddStr( args, "download-dir", myDestination.absolutePath().toUtf8().constData() );
265
QFile file( myFile );
266
file.open( QIODevice::ReadOnly );
267
const QByteArray metainfo( file.readAll( ) );
270
char * base64 = tr_base64_encode( metainfo.constData(), metainfo.size(), &base64Size );
271
tr_bencDictAddRaw( args, "metainfo", base64, base64Size );
275
tr_bencDictAddBool( args, "paused", !myStartCheck->isChecked( ) );
278
int count = myWanted.count( false );
280
tr_benc * l = tr_bencDictAddList( args, "files-unwanted", count );
281
for( int i=0, n=myWanted.size(); i<n; ++i )
282
if( myWanted.at(i) == false )
283
tr_bencListAddInt( l, i );
287
count = myPriorities.count( TR_PRI_LOW );
289
tr_benc * l = tr_bencDictAddList( args, "priority-low", count );
290
for( int i=0, n=myPriorities.size(); i<n; ++i )
291
if( myPriorities.at(i) == TR_PRI_LOW )
292
tr_bencListAddInt( l, i );
296
count = myPriorities.count( TR_PRI_HIGH );
298
tr_benc * l = tr_bencDictAddList( args, "priority-high", count );
299
for( int i=0, n=myPriorities.size(); i<n; ++i )
300
if( myPriorities.at(i) == TR_PRI_HIGH )
301
tr_bencListAddInt( l, i );
304
mySession.exec( &top );
309
// maybe the source .torrent
310
if( myTrashCheck->isChecked( ) )
311
QFile(myFile).remove( );
315
Options :: onFilenameClicked( )
317
QFileDialog * d = new QFileDialog( this,
319
QFileInfo(myFile).absolutePath(),
320
tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
321
d->setFileMode( QFileDialog::ExistingFile );
322
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onFilesSelected(const QStringList&)) );
327
Options :: onFilesSelected( const QStringList& files )
329
if( files.size() == 1 )
331
myFile = files.at( 0 );
332
refreshFileButton( );
338
Options :: onDestinationClicked( )
340
QFileDialog * d = new QFileDialog( this,
341
tr( "Select Destination" ),
342
myDestination.absolutePath( ) );
343
d->setFileMode( QFileDialog::Directory );
344
connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onDestinationsSelected(const QStringList&)) );
349
Options :: onDestinationsSelected( const QStringList& destinations )
351
if( destinations.size() == 1 )
353
const QString& destination( destinations.first( ) );
354
myDestination.setPath( destination );
355
refreshDestinationButton( );
366
Options :: clearVerify( )
368
myVerifyHash.reset( );
369
myVerifyFile.close( );
371
myVerifyFlags.clear( );
372
myVerifyFileIndex = 0;
373
myVerifyPieceIndex = 0;
374
myVerifyPiecePos = 0;
375
myVerifyTimer.stop( );
377
for( int i=0, n=myFiles.size(); i<n; ++i )
379
myTree->update( myFiles );
383
Options :: onVerify( )
385
//std::cerr << "starting to verify..." << std::endl;
387
myVerifyFlags.insert( 0, myInfo.pieceCount, false );
388
myVerifyTimer.setSingleShot( false );
389
myVerifyTimer.start( 0 );
394
uint64_t getPieceSize( const tr_info * info, tr_piece_index_t pieceIndex )
396
if( pieceIndex != info->pieceCount - 1 )
397
return info->pieceSize;
398
return info->totalSize % info->pieceSize;
403
Options :: onTimeout( )
405
const tr_file * file = &myInfo.files[myVerifyFileIndex];
407
if( !myVerifyFilePos && !myVerifyFile.isOpen( ) )
409
const QFileInfo fileInfo( myDestination, QString::fromUtf8( file->name ) );
410
myVerifyFile.setFileName( fileInfo.absoluteFilePath( ) );
411
//std::cerr << "opening file" << qPrintable(fileInfo.absoluteFilePath()) << std::endl;
412
myVerifyFile.open( QIODevice::ReadOnly );
415
int64_t leftInPiece = getPieceSize( &myInfo, myVerifyPieceIndex ) - myVerifyPiecePos;
416
int64_t leftInFile = file->length - myVerifyFilePos;
417
int64_t bytesThisPass = std::min( leftInFile, leftInPiece );
418
bytesThisPass = std::min( bytesThisPass, (int64_t)sizeof( myVerifyBuf ) );
420
if( myVerifyFile.isOpen() && myVerifyFile.seek( myVerifyFilePos ) ) {
421
int64_t numRead = myVerifyFile.read( myVerifyBuf, bytesThisPass );
422
if( numRead == bytesThisPass )
423
myVerifyHash.addData( myVerifyBuf, numRead );
426
leftInPiece -= bytesThisPass;
427
leftInFile -= bytesThisPass;
428
myVerifyPiecePos += bytesThisPass;
429
myVerifyFilePos += bytesThisPass;
431
myVerifyBins[myVerifyFileIndex] += bytesThisPass;
433
if( leftInPiece == 0 )
435
const QByteArray result( myVerifyHash.result( ) );
436
const bool matches = !memcmp( result.constData(),
437
myInfo.pieces[myVerifyPieceIndex].hash,
439
myVerifyFlags[myVerifyPieceIndex] = matches;
440
myVerifyPiecePos = 0;
441
++myVerifyPieceIndex;
442
myVerifyHash.reset( );
444
FileList changedFiles;
446
mybins_t::const_iterator i;
447
for( i=myVerifyBins.begin(); i!=myVerifyBins.end(); ++i ) {
448
TrFile& f( myFiles[i.key( )] );
449
f.have += i.value( );
450
changedFiles.append( f );
453
myTree->update( changedFiles );
454
myVerifyBins.clear( );
457
if( leftInFile == 0 )
459
//std::cerr << "closing file" << std::endl;
460
myVerifyFile.close( );
465
const bool done = myVerifyPieceIndex >= myInfo.pieceCount;
468
myVerifyTimer.stop( );