source: trunk/qt/options.cc @ 8327

Last change on this file since 8327 was 8327, checked in by charles, 7 years ago

(trunk qt) session dialog improvements from W4pp

File size: 13.4 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:$
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
46Options :: 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( "&Move source file to Trash" ) );
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    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
314void
315Options :: 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
326void
327Options :: onFilesSelected( const QStringList& files )
328{
329    if( files.size() == 1 )
330    {
331        myFile = files.at( 0 );
332        refreshFileButton( );
333        reload( );
334    }
335}
336
337void
338Options :: 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
348void
349Options :: 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
365void
366Options :: 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
382void
383Options :: 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
392namespace
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
402void
403Options :: 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}
Note: See TracBrowser for help on using the repository browser.