~ubuntu-branches/ubuntu/precise/transmission/precise

« back to all changes in this revision

Viewing changes to qt/options.cc

  • Committer: Bazaar Package Importer
  • Author(s): Leo Costela
  • Date: 2009-05-17 19:39:51 UTC
  • mto: (1.3.4 upstream) (2.2.1 squeeze)
  • mto: This revision was merged to the branch mainline in revision 36.
  • Revision ID: james.westby@ubuntu.com-20090517193951-k8x15sqoxzf7cuyx
ImportĀ upstreamĀ versionĀ 1.61

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This file Copyright (C) 2009 Charles Kerr <charles@transmissionbt.com>
 
3
 *
 
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.
 
9
 *
 
10
 * $Id:$
 
11
 */
 
12
 
 
13
#include <cstdio>
 
14
#include <iostream>
 
15
 
 
16
#include <QEvent>
 
17
#include <QResizeEvent>
 
18
#include <QFileDialog>
 
19
#include <QGridLayout>
 
20
#include <QLabel>
 
21
#include <QCheckBox>
 
22
#include <QFileInfo>
 
23
#include <QDialogButtonBox>
 
24
#include <QPushButton>
 
25
#include <QLabel>
 
26
#include <QSet>
 
27
#include <QWidget>
 
28
#include <QVBoxLayout>
 
29
 
 
30
#include <libtransmission/transmission.h>
 
31
#include <libtransmission/bencode.h>
 
32
#include <libtransmission/utils.h> /* mime64 */
 
33
 
 
34
#include "file-tree.h"
 
35
#include "hig.h"
 
36
#include "options.h"
 
37
#include "prefs.h"
 
38
#include "qticonloader.h"
 
39
#include "session.h"
 
40
#include "torrent.h"
 
41
 
 
42
/***
 
43
****
 
44
***/
 
45
 
 
46
Options :: Options( Session& session, const Prefs& prefs, const QString& filename, QWidget * parent ):
 
47
    QDialog( parent, Qt::Dialog ),
 
48
    mySession( session ),
 
49
    myFile( filename ),
 
50
    myHaveInfo( false ),
 
51
    myDestinationButton( 0 ),
 
52
    myVerifyButton( 0 ),
 
53
    myVerifyFile( 0 ),
 
54
    myVerifyHash( QCryptographicHash::Sha1 )
 
55
 
 
56
{
 
57
    setWindowTitle( tr( "Add Torrent" ) );
 
58
    QFontMetrics fontMetrics( font( ) );
 
59
    QGridLayout * layout = new QGridLayout( this );
 
60
    int row = 0;
 
61
 
 
62
    const int iconSize( style( )->pixelMetric( QStyle :: PM_SmallIconSize ) );
 
63
    QIcon fileIcon = style( )->standardIcon( QStyle::SP_FileIcon );
 
64
    const QPixmap filePixmap = fileIcon.pixmap( iconSize );
 
65
 
 
66
    QPushButton * p;
 
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 );
 
75
 
 
76
    layout->addWidget( p, row, 1 );
 
77
    l->setBuddy( p );
 
78
    connect( p, SIGNAL(clicked(bool)), this, SLOT(onFilenameClicked()));
 
79
  
 
80
    if( session.isLocal( ) ) 
 
81
    {
 
82
        const QIcon folderIcon = QtIconLoader :: icon( "folder", style()->standardIcon( QStyle::SP_DirIcon ) );
 
83
        const QPixmap folderPixmap = folderIcon.pixmap( iconSize );
 
84
 
 
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 );
 
93
        l->setBuddy( p );
 
94
        connect( p, SIGNAL(clicked(bool)), this, SLOT(onDestinationClicked()));
 
95
    }
 
96
 
 
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
 
101
 
 
102
    if( session.isLocal( ) )
 
103
    {
 
104
        p = myVerifyButton = new QPushButton( tr( "&Verify Local Data" ) );
 
105
        layout->addWidget( p, ++row, 0, Qt::AlignLeft );
 
106
    }
 
107
 
 
108
    QCheckBox * c;
 
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 );
 
112
 
 
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 );
 
116
 
 
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 );
 
121
 
 
122
    layout->setRowStretch( 2, 2 );
 
123
    layout->setColumnStretch( 1, 2 );
 
124
    layout->setSpacing( HIG :: PAD );
 
125
 
 
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()));
 
130
 
 
131
    connect( &myVerifyTimer, SIGNAL(timeout()), this, SLOT(onTimeout()));
 
132
 
 
133
    reload( );
 
134
}
 
135
    
 
136
Options :: ~Options( )
 
137
{
 
138
    clearInfo( );
 
139
}
 
140
 
 
141
/***
 
142
****
 
143
***/
 
144
 
 
145
void
 
146
Options :: refreshButton( QPushButton * p, const QString& text, int width )
 
147
{
 
148
    if( width <= 0 ) width = p->width( );
 
149
    width -= 15;
 
150
    QFontMetrics fontMetrics( font( ) );
 
151
    QString str = fontMetrics.elidedText( text, Qt::ElideRight, width );
 
152
    p->setText( str );
 
153
}
 
154
 
 
155
void
 
156
Options :: refreshFileButton( int width )
 
157
{
 
158
    refreshButton( myFileButton, QFileInfo(myFile).baseName(), width );
 
159
}
 
160
 
 
161
void
 
162
Options :: refreshDestinationButton( int width )
 
163
{
 
164
    if( myDestinationButton != 0 )
 
165
        refreshButton( myDestinationButton, myDestination.absolutePath(), width );
 
166
}
 
167
 
 
168
 
 
169
bool
 
170
Options :: eventFilter( QObject * o, QEvent * event )
 
171
{
 
172
    if( o==myFileButton && event->type() == QEvent::Resize )
 
173
    {
 
174
        refreshFileButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
 
175
    }
 
176
 
 
177
    if( o==myDestinationButton && event->type() == QEvent::Resize )
 
178
    {
 
179
        refreshDestinationButton( dynamic_cast<QResizeEvent*>(event)->size().width() );
 
180
    }
 
181
 
 
182
    return false;
 
183
}
 
184
 
 
185
/***
 
186
****
 
187
***/
 
188
 
 
189
void
 
190
Options :: clearInfo( )
 
191
{
 
192
    if( myHaveInfo )
 
193
        tr_metainfoFree( &myInfo );
 
194
    myHaveInfo = false;
 
195
    myFiles.clear( );
 
196
}
 
197
 
 
198
void
 
199
Options :: reload( )
 
200
{
 
201
    clearInfo( );
 
202
    clearVerify( );
 
203
 
 
204
    tr_ctor * ctor = tr_ctorNew( 0 );
 
205
    tr_ctorSetMetainfoFromFile( ctor, myFile.toUtf8().constData() );
 
206
    const int err = tr_torrentParse( ctor, &myInfo );
 
207
    myHaveInfo = !err;
 
208
    tr_ctorFree( ctor );
 
209
 
 
210
    myTree->clear( );
 
211
    myFiles.clear( );
 
212
    myPriorities.clear( );
 
213
    myWanted.clear( );
 
214
 
 
215
    if( myHaveInfo )
 
216
    {
 
217
        myPriorities.insert( 0, myInfo.fileCount, TR_PRI_NORMAL );
 
218
        myWanted.insert( 0, myInfo.fileCount, true );
 
219
 
 
220
        for( tr_file_index_t i=0; i<myInfo.fileCount; ++i ) {
 
221
            TrFile file;
 
222
            file.index = i;
 
223
            file.priority = myPriorities[i];
 
224
            file.wanted = myWanted[i];
 
225
            file.size = myInfo.files[i].length;
 
226
            file.have = 0;
 
227
            file.filename = QString::fromUtf8( myInfo.files[i].name );
 
228
            myFiles.append( file );
 
229
        }
 
230
    }
 
231
 
 
232
    myTree->update( myFiles );
 
233
}
 
234
 
 
235
void
 
236
Options :: onPriorityChanged( const QSet<int>& fileIndices, int priority )
 
237
{
 
238
    foreach( int i, fileIndices )
 
239
        myPriorities[i] = priority;
 
240
}
 
241
 
 
242
void
 
243
Options :: onWantedChanged( const QSet<int>& fileIndices, bool isWanted )
 
244
{
 
245
    foreach( int i, fileIndices )
 
246
        myWanted[i] = isWanted;
 
247
}
 
248
 
 
249
void
 
250
Options :: onAccepted( )
 
251
{
 
252
    // rpc spec section 3.4 "adding a torrent"
 
253
 
 
254
    tr_benc top;
 
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 ) );
 
259
 
 
260
    // "download-dir"
 
261
    if( myDestinationButton )
 
262
        tr_bencDictAddStr( args, "download-dir", myDestination.absolutePath().toUtf8().constData() );
 
263
 
 
264
    // "metainfo"
 
265
    QFile file( myFile );
 
266
    file.open( QIODevice::ReadOnly );
 
267
    const QByteArray metainfo( file.readAll( ) );
 
268
    file.close( );
 
269
    int base64Size = 0;
 
270
    char * base64 = tr_base64_encode( metainfo.constData(), metainfo.size(), &base64Size );
 
271
    tr_bencDictAddRaw( args, "metainfo", base64, base64Size );
 
272
    tr_free( base64 );
 
273
 
 
274
    // paused
 
275
    tr_bencDictAddBool( args, "paused", !myStartCheck->isChecked( ) );
 
276
 
 
277
    // files-unwanted
 
278
    int count = myWanted.count( false );
 
279
    if( count > 0 ) {
 
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 );
 
284
    }
 
285
 
 
286
    // priority-low
 
287
    count = myPriorities.count( TR_PRI_LOW );
 
288
    if( count > 0 ) {
 
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 );
 
293
    }
 
294
 
 
295
    // priority-high
 
296
    count = myPriorities.count( TR_PRI_HIGH );
 
297
    if( count > 0 ) {
 
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 );
 
302
    }
 
303
 
 
304
    mySession.exec( &top );
 
305
 
 
306
    tr_bencFree( &top );
 
307
    deleteLater( );
 
308
 
 
309
    // maybe the source .torrent
 
310
    if( myTrashCheck->isChecked( ) )
 
311
        QFile(myFile).remove( );
 
312
}
 
313
 
 
314
void
 
315
Options :: onFilenameClicked( )
 
316
{
 
317
    QFileDialog * d = new QFileDialog( this,
 
318
                                       tr( "Add Torrent" ),
 
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&)) );
 
323
    d->show( );
 
324
}
 
325
 
 
326
void
 
327
Options :: onFilesSelected( const QStringList& files )
 
328
{
 
329
    if( files.size() == 1 )
 
330
    {
 
331
        myFile = files.at( 0 );
 
332
        refreshFileButton( );
 
333
        reload( );
 
334
    }
 
335
}
 
336
 
 
337
void
 
338
Options :: onDestinationClicked( )
 
339
{
 
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&)) );
 
345
    d->show( );
 
346
}
 
347
 
 
348
void
 
349
Options :: onDestinationsSelected( const QStringList& destinations )
 
350
{
 
351
    if( destinations.size() == 1 )
 
352
    {
 
353
        const QString& destination( destinations.first( ) );
 
354
        myDestination.setPath( destination );
 
355
        refreshDestinationButton( );
 
356
    }
 
357
}
 
358
 
 
359
/***
 
360
****
 
361
****  VERIFY
 
362
****
 
363
***/
 
364
 
 
365
void
 
366
Options :: clearVerify( )
 
367
{
 
368
    myVerifyHash.reset( );
 
369
    myVerifyFile.close( );
 
370
    myVerifyFilePos = 0;
 
371
    myVerifyFlags.clear( );
 
372
    myVerifyFileIndex = 0;
 
373
    myVerifyPieceIndex = 0;
 
374
    myVerifyPiecePos = 0;
 
375
    myVerifyTimer.stop( );
 
376
 
 
377
    for( int i=0, n=myFiles.size(); i<n; ++i )
 
378
        myFiles[i].have = 0;
 
379
    myTree->update( myFiles );
 
380
}
 
381
 
 
382
void
 
383
Options :: onVerify( )
 
384
{
 
385
    //std::cerr << "starting to verify..." << std::endl;
 
386
    clearVerify( );
 
387
    myVerifyFlags.insert( 0, myInfo.pieceCount, false );
 
388
    myVerifyTimer.setSingleShot( false );
 
389
    myVerifyTimer.start( 0 );
 
390
}
 
391
 
 
392
namespace
 
393
{
 
394
    uint64_t getPieceSize( const tr_info * info, tr_piece_index_t pieceIndex )
 
395
    {
 
396
        if( pieceIndex != info->pieceCount - 1 )
 
397
            return info->pieceSize;
 
398
        return info->totalSize % info->pieceSize;
 
399
    }
 
400
}
 
401
 
 
402
void
 
403
Options :: onTimeout( )
 
404
{
 
405
    const tr_file * file = &myInfo.files[myVerifyFileIndex];
 
406
 
 
407
    if( !myVerifyFilePos && !myVerifyFile.isOpen( ) )
 
408
    {
 
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 );
 
413
    }
 
414
 
 
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 ) );
 
419
 
 
420
    if( myVerifyFile.isOpen() && myVerifyFile.seek( myVerifyFilePos ) ) {
 
421
        int64_t numRead = myVerifyFile.read( myVerifyBuf, bytesThisPass );
 
422
        if( numRead == bytesThisPass )
 
423
            myVerifyHash.addData( myVerifyBuf, numRead );
 
424
    }
 
425
 
 
426
    leftInPiece -= bytesThisPass;
 
427
    leftInFile -= bytesThisPass;
 
428
    myVerifyPiecePos += bytesThisPass;
 
429
    myVerifyFilePos += bytesThisPass;
 
430
 
 
431
    myVerifyBins[myVerifyFileIndex] += bytesThisPass;
 
432
 
 
433
    if( leftInPiece == 0 )
 
434
    {
 
435
        const QByteArray result( myVerifyHash.result( ) );
 
436
        const bool matches = !memcmp( result.constData(),
 
437
                                      myInfo.pieces[myVerifyPieceIndex].hash,
 
438
                                      SHA_DIGEST_LENGTH );
 
439
        myVerifyFlags[myVerifyPieceIndex] = matches;
 
440
        myVerifyPiecePos = 0;
 
441
        ++myVerifyPieceIndex;
 
442
        myVerifyHash.reset( );
 
443
 
 
444
        FileList changedFiles;
 
445
        if( matches ) {
 
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 );
 
451
            }
 
452
        }
 
453
        myTree->update( changedFiles );
 
454
        myVerifyBins.clear( );
 
455
    }
 
456
 
 
457
    if( leftInFile == 0 )
 
458
    {
 
459
        //std::cerr << "closing file" << std::endl;
 
460
        myVerifyFile.close( );
 
461
        ++myVerifyFileIndex;
 
462
        myVerifyFilePos = 0;
 
463
    }
 
464
 
 
465
    const bool done = myVerifyPieceIndex >= myInfo.pieceCount;
 
466
 
 
467
    if( done )
 
468
        myVerifyTimer.stop( );
 
469
}