source: trunk/qt/options.cc @ 9247

Last change on this file since 9247 was 9247, checked in by charles, 13 years ago

(trunk qt) minor cleanup

  • Property svn:keywords set to Date Rev Author Id
File size: 13.6 KB
Line 
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: options.cc 9247 2009-10-06 00:27:26Z charles $
11 */
12
13#include <cstdio>
14#include <iostream>
15
16#include <QCheckBox>
17#include <QDialogButtonBox>
18#include <QEvent>
19#include <QFileDialog>
20#include <QFileIconProvider>
21#include <QFileInfo>
22#include <QGridLayout>
23#include <QLabel>
24#include <QPushButton>
25#include <QResizeEvent>
26#include <QSet>
27#include <QVBoxLayout>
28#include <QWidget>
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 "session.h"
39#include "torrent.h"
40
41/***
42****
43***/
44
45Options :: Options( Session& session, const Prefs& prefs, const QString& filename, QWidget * parent ):
46    QDialog( parent, Qt::Dialog ),
47    mySession( session ),
48    myFile( filename ),
49    myHaveInfo( false ),
50    myDestinationButton( 0 ),
51    myVerifyButton( 0 ),
52    myVerifyFile( 0 ),
53    myVerifyHash( QCryptographicHash::Sha1 )
54
55{
56    setWindowTitle( tr( "Add Torrent" ) );
57    QFontMetrics fontMetrics( font( ) );
58    QGridLayout * layout = new QGridLayout( this );
59    int row = 0;
60
61    const int iconSize( style( )->pixelMetric( QStyle :: PM_SmallIconSize ) );
62    QIcon fileIcon = style( )->standardIcon( QStyle::SP_FileIcon );
63    const QPixmap filePixmap = fileIcon.pixmap( iconSize );
64
65    QPushButton * p;
66    int width = fontMetrics.size( 0, "This is a pretty long torrent filename indeed.torrent" ).width( );
67    QLabel * l = new QLabel( tr( "&Torrent file:" ) );
68    layout->addWidget( l, row, 0, Qt::AlignLeft );
69    p = myFileButton =  new QPushButton;
70    p->setIcon( filePixmap );
71    p->setMinimumWidth( width );
72    p->setStyleSheet( "text-align: left; padding-left: 5; padding-right: 5" );
73    p->installEventFilter( this );
74
75    layout->addWidget( p, row, 1 );
76    l->setBuddy( p );
77    connect( p, SIGNAL(clicked(bool)), this, SLOT(onFilenameClicked()));
78 
79    if( session.isLocal( ) ) 
80    {
81        const QFileIconProvider iconProvider;
82        const QIcon folderIcon = iconProvider.icon( QFileIconProvider::Folder );
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   
136Options :: ~Options( )
137{
138    clearInfo( );
139}
140
141/***
142****
143***/
144
145void
146Options :: 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
155void
156Options :: refreshFileButton( int width )
157{
158    refreshButton( myFileButton, QFileInfo(myFile).baseName(), width );
159}
160
161void
162Options :: refreshDestinationButton( int width )
163{
164    if( myDestinationButton != 0 )
165        refreshButton( myDestinationButton, myDestination.absolutePath(), width );
166}
167
168
169bool
170Options :: 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
189void
190Options :: clearInfo( )
191{
192    if( myHaveInfo )
193        tr_metainfoFree( &myInfo );
194    myHaveInfo = false;
195    myFiles.clear( );
196}
197
198void
199Options :: 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
235void
236Options :: onPriorityChanged( const QSet<int>& fileIndices, int priority )
237{
238    foreach( int i, fileIndices )
239        myPriorities[i] = priority;
240}
241
242void
243Options :: onWantedChanged( const QSet<int>& fileIndices, bool isWanted )
244{
245    foreach( int i, fileIndices )
246        myWanted[i] = isWanted;
247}
248
249void
250Options :: onAccepted( )
251{
252    // rpc spec section 3.4 "adding a torrent"
253
254    const int64_t tag = mySession.getUniqueTag( );
255    tr_benc top;
256    tr_bencInitDict( &top, 3 );
257    tr_bencDictAddStr( &top, "method", "torrent-add" );
258    tr_bencDictAddInt( &top, "tag", tag );
259    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 10 ) );
260
261    // "download-dir"
262    if( myDestinationButton )
263        tr_bencDictAddStr( args, "download-dir", myDestination.absolutePath().toUtf8().constData() );
264
265    // "metainfo"
266    QFile file( myFile );
267    file.open( QIODevice::ReadOnly );
268    const QByteArray metainfo( file.readAll( ) );
269    file.close( );
270    int base64Size = 0;
271    char * base64 = tr_base64_encode( metainfo.constData(), metainfo.size(), &base64Size );
272    tr_bencDictAddRaw( args, "metainfo", base64, base64Size );
273    tr_free( base64 );
274
275    // paused
276    tr_bencDictAddBool( args, "paused", !myStartCheck->isChecked( ) );
277
278    // files-unwanted
279    int count = myWanted.count( false );
280    if( count > 0 ) {
281        tr_benc * l = tr_bencDictAddList( args, "files-unwanted", count );
282        for( int i=0, n=myWanted.size(); i<n; ++i )
283            if( myWanted.at(i) == false )
284                tr_bencListAddInt( l, i );
285    }
286
287    // priority-low
288    count = myPriorities.count( TR_PRI_LOW );
289    if( count > 0 ) {
290        tr_benc * l = tr_bencDictAddList( args, "priority-low", count );
291        for( int i=0, n=myPriorities.size(); i<n; ++i )
292            if( myPriorities.at(i) == TR_PRI_LOW )
293                tr_bencListAddInt( l, i );
294    }
295
296    // priority-high
297    count = myPriorities.count( TR_PRI_HIGH );
298    if( count > 0 ) {
299        tr_benc * l = tr_bencDictAddList( args, "priority-high", count );
300        for( int i=0, n=myPriorities.size(); i<n; ++i )
301            if( myPriorities.at(i) == TR_PRI_HIGH )
302                tr_bencListAddInt( l, i );
303    }
304
305    // maybe delete the source .torrent
306    if( myTrashCheck->isChecked( ) ) {
307        FileAdded * fileAdded = new FileAdded( tag, myFile );
308        connect( &mySession, SIGNAL(executed(int64_t,const QString&, struct tr_benc*)),
309                 fileAdded, SLOT(executed(int64_t,const QString&, struct tr_benc*)));
310    }
311
312    mySession.exec( &top );
313
314    tr_bencFree( &top );
315    deleteLater( );
316}
317
318void
319Options :: onFilenameClicked( )
320{
321    QFileDialog * d = new QFileDialog( this,
322                                       tr( "Add Torrent" ),
323                                       QFileInfo(myFile).absolutePath(),
324                                       tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
325    d->setFileMode( QFileDialog::ExistingFile );
326    connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onFilesSelected(const QStringList&)) );
327    d->show( );
328}
329
330void
331Options :: onFilesSelected( const QStringList& files )
332{
333    if( files.size() == 1 )
334    {
335        myFile = files.at( 0 );
336        refreshFileButton( );
337        reload( );
338    }
339}
340
341void
342Options :: onDestinationClicked( )
343{
344    QFileDialog * d = new QFileDialog( this,
345                                       tr( "Select Destination" ),
346                                       myDestination.absolutePath( ) );
347    d->setFileMode( QFileDialog::Directory );
348    connect( d, SIGNAL(filesSelected(const QStringList&)), this, SLOT(onDestinationsSelected(const QStringList&)) );
349    d->show( );
350}
351
352void
353Options :: onDestinationsSelected( const QStringList& destinations )
354{
355    if( destinations.size() == 1 )
356    {
357        const QString& destination( destinations.first( ) );
358        myDestination.setPath( destination );
359        refreshDestinationButton( );
360    }
361}
362
363/***
364****
365****  VERIFY
366****
367***/
368
369void
370Options :: clearVerify( )
371{
372    myVerifyHash.reset( );
373    myVerifyFile.close( );
374    myVerifyFilePos = 0;
375    myVerifyFlags.clear( );
376    myVerifyFileIndex = 0;
377    myVerifyPieceIndex = 0;
378    myVerifyPiecePos = 0;
379    myVerifyTimer.stop( );
380
381    for( int i=0, n=myFiles.size(); i<n; ++i )
382        myFiles[i].have = 0;
383    myTree->update( myFiles );
384}
385
386void
387Options :: onVerify( )
388{
389    //std::cerr << "starting to verify..." << std::endl;
390    clearVerify( );
391    myVerifyFlags.insert( 0, myInfo.pieceCount, false );
392    myVerifyTimer.setSingleShot( false );
393    myVerifyTimer.start( 0 );
394}
395
396namespace
397{
398    uint64_t getPieceSize( const tr_info * info, tr_piece_index_t pieceIndex )
399    {
400        if( pieceIndex != info->pieceCount - 1 )
401            return info->pieceSize;
402        return info->totalSize % info->pieceSize;
403    }
404}
405
406void
407Options :: onTimeout( )
408{
409    const tr_file * file = &myInfo.files[myVerifyFileIndex];
410
411    if( !myVerifyFilePos && !myVerifyFile.isOpen( ) )
412    {
413        const QFileInfo fileInfo( myDestination, QString::fromUtf8( file->name ) );
414        myVerifyFile.setFileName( fileInfo.absoluteFilePath( ) );
415        //std::cerr << "opening file" << qPrintable(fileInfo.absoluteFilePath()) << std::endl;
416        myVerifyFile.open( QIODevice::ReadOnly );
417    }
418
419    int64_t leftInPiece = getPieceSize( &myInfo, myVerifyPieceIndex ) - myVerifyPiecePos;
420    int64_t leftInFile = file->length - myVerifyFilePos;
421    int64_t bytesThisPass = std::min( leftInFile, leftInPiece );
422    bytesThisPass = std::min( bytesThisPass, (int64_t)sizeof( myVerifyBuf ) );
423
424    if( myVerifyFile.isOpen() && myVerifyFile.seek( myVerifyFilePos ) ) {
425        int64_t numRead = myVerifyFile.read( myVerifyBuf, bytesThisPass );
426        if( numRead == bytesThisPass )
427            myVerifyHash.addData( myVerifyBuf, numRead );
428    }
429
430    leftInPiece -= bytesThisPass;
431    leftInFile -= bytesThisPass;
432    myVerifyPiecePos += bytesThisPass;
433    myVerifyFilePos += bytesThisPass;
434
435    myVerifyBins[myVerifyFileIndex] += bytesThisPass;
436
437    if( leftInPiece == 0 )
438    {
439        const QByteArray result( myVerifyHash.result( ) );
440        const bool matches = !memcmp( result.constData(),
441                                      myInfo.pieces[myVerifyPieceIndex].hash,
442                                      SHA_DIGEST_LENGTH );
443        myVerifyFlags[myVerifyPieceIndex] = matches;
444        myVerifyPiecePos = 0;
445        ++myVerifyPieceIndex;
446        myVerifyHash.reset( );
447
448        FileList changedFiles;
449        if( matches ) {
450            mybins_t::const_iterator i;
451            for( i=myVerifyBins.begin(); i!=myVerifyBins.end(); ++i ) {
452                TrFile& f( myFiles[i.key( )] );
453                f.have += i.value( );
454                changedFiles.append( f );
455            }
456        }
457        myTree->update( changedFiles );
458        myVerifyBins.clear( );
459    }
460
461    if( leftInFile == 0 )
462    {
463        //std::cerr << "closing file" << std::endl;
464        myVerifyFile.close( );
465        ++myVerifyFileIndex;
466        myVerifyFilePos = 0;
467    }
468
469    const bool done = myVerifyPieceIndex >= myInfo.pieceCount;
470
471    if( done )
472        myVerifyTimer.stop( );
473}
Note: See TracBrowser for help on using the repository browser.