source: trunk/qt/torrent.cc @ 10077

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

(trunk GTK+, Qt) #2645 "add torrent availability to Properties->Information" -- added for 1.90

  • Property svn:keywords set to Date Rev Author Id
File size: 17.6 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
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: torrent.cc 10077 2010-02-02 05:34:26Z charles $
11 */
12
13#include <cassert>
14#include <iostream>
15
16#include <QApplication>
17#include <QFileIconProvider>
18#include <QFileInfo>
19#include <QSet>
20#include <QString>
21#include <QStyle>
22#include <QVariant>
23
24#include <libtransmission/transmission.h>
25#include <libtransmission/bencode.h>
26#include <libtransmission/utils.h> /* tr_new0, tr_strdup */
27
28#include "app.h"
29#include "prefs.h"
30#include "torrent.h"
31#include "utils.h"
32
33
34Torrent :: Torrent( Prefs& prefs, int id ):
35    myPrefs( prefs )
36{
37    for( int i=0; i<PROPERTY_COUNT; ++i )
38        assert( myProperties[i].id == i );
39
40    setInt( ID, id );
41    setIcon( MIME_ICON, QApplication::style()->standardIcon( QStyle::SP_FileIcon ) );
42}
43
44Torrent :: ~Torrent( )
45{
46}
47
48/***
49****
50***/
51
52Torrent :: Property
53Torrent :: myProperties[] =
54{
55    { ID, "id", QVariant::Int, INFO, },
56    { UPLOAD_SPEED, "rateUpload", QVariant::Int, STAT } /* B/s */,
57    { DOWNLOAD_SPEED, "rateDownload", QVariant::Int, STAT }, /* B/s */
58    { DOWNLOAD_DIR, "downloadDir", QVariant::String, STAT },
59    { ACTIVITY, "status", QVariant::Int, STAT },
60    { NAME, "name", QVariant::String, INFO },
61    { ERROR, "error", QVariant::Int, STAT },
62    { ERROR_STRING, "errorString", QVariant::String, STAT },
63    { SIZE_WHEN_DONE, "sizeWhenDone", QVariant::ULongLong, STAT },
64    { LEFT_UNTIL_DONE, "leftUntilDone", QVariant::ULongLong, STAT },
65    { HAVE_UNCHECKED, "haveUnchecked", QVariant::ULongLong, STAT },
66    { HAVE_VERIFIED, "haveValid", QVariant::ULongLong, STAT },
67    { DESIRED_AVAILABLE, "desiredAvailable", QVariant::ULongLong, STAT },
68    { TOTAL_SIZE, "totalSize", QVariant::ULongLong, INFO },
69    { PIECE_SIZE, "pieceSize", QVariant::ULongLong, INFO },
70    { PIECE_COUNT, "pieceCount", QVariant::Int, INFO },
71    { PEERS_GETTING_FROM_US, "peersGettingFromUs", QVariant::Int, STAT },
72    { PEERS_SENDING_TO_US, "peersSendingToUs", QVariant::Int, STAT },
73    { WEBSEEDS_SENDING_TO_US, "webseedsSendingToUs", QVariant::Int, STAT_EXTRA },
74    { PERCENT_DONE, "percentDone", QVariant::Double, STAT },
75    { METADATA_PERCENT_DONE, "metadataPercentComplete", QVariant::Double, STAT },
76    { PERCENT_VERIFIED, "recheckProgress", QVariant::Double, STAT },
77    { DATE_ACTIVITY, "activityDate", QVariant::DateTime, STAT_EXTRA },
78    { DATE_ADDED, "addedDate", QVariant::DateTime, INFO },
79    { DATE_STARTED, "startDate", QVariant::DateTime, STAT_EXTRA },
80    { DATE_CREATED, "dateCreated", QVariant::DateTime, INFO },
81    { PEERS_CONNECTED, "peersConnected", QVariant::Int, STAT },
82    { ETA, "eta", QVariant::Int, STAT },
83    { RATIO, "uploadRatio", QVariant::Double, STAT },
84    { DOWNLOADED_EVER, "downloadedEver", QVariant::ULongLong, STAT },
85    { UPLOADED_EVER, "uploadedEver", QVariant::ULongLong, STAT },
86    { FAILED_EVER, "corruptEver", QVariant::ULongLong, STAT_EXTRA },
87    { TRACKERS, "trackers", QVariant::StringList, INFO },
88    { MIME_ICON, "ccc", QVariant::Icon, DERIVED },
89    { SEED_RATIO_LIMIT, "seedRatioLimit", QVariant::Double, STAT_EXTRA },
90    { SEED_RATIO_MODE, "seedRatioMode", QVariant::Int, STAT_EXTRA },
91    { DOWN_LIMIT, "downloadLimit", QVariant::Int, STAT_EXTRA }, /* KB/s */
92    { DOWN_LIMITED, "downloadLimited", QVariant::Bool, STAT_EXTRA },
93    { UP_LIMIT, "uploadLimit", QVariant::Int, STAT_EXTRA }, /* KB/s */
94    { UP_LIMITED, "uploadLimited", QVariant::Bool, STAT_EXTRA },
95    { HONORS_SESSION_LIMITS, "honorsSessionLimits", QVariant::Bool, STAT_EXTRA },
96    { PEER_LIMIT, "peer-limit", QVariant::Int, STAT_EXTRA },
97    { HASH_STRING, "hashString", QVariant::String, INFO },
98    { IS_PRIVATE, "isPrivate", QVariant::Bool, INFO },
99    { COMMENT, "comment", QVariant::String, INFO },
100    { CREATOR, "creator", QVariant::String, INFO },
101    { LAST_ANNOUNCE_TIME, "lastAnnounceTime", QVariant::DateTime, STAT_EXTRA },
102    { LAST_SCRAPE_TIME, "lastScrapeTime", QVariant::DateTime, STAT_EXTRA },
103    { MANUAL_ANNOUNCE_TIME, "manualAnnounceTime", QVariant::DateTime, STAT_EXTRA },
104    { NEXT_ANNOUNCE_TIME, "nextAnnounceTime", QVariant::DateTime, STAT_EXTRA },
105    { NEXT_SCRAPE_TIME, "nextScrapeTime", QVariant::DateTime, STAT_EXTRA },
106    { SCRAPE_RESPONSE, "scrapeResponse", QVariant::String, STAT_EXTRA },
107    { ANNOUNCE_RESPONSE, "announceResponse", QVariant::String, STAT_EXTRA },
108    { ANNOUNCE_URL, "announceURL", QVariant::String, STAT_EXTRA },
109    { PEERS, "peers", TrTypes::PeerList, STAT_EXTRA },
110    { TORRENT_FILE, "torrentFile", QVariant::String, STAT_EXTRA },
111    { BANDWIDTH_PRIORITY, "bandwidthPriority", QVariant::Int, STAT_EXTRA }
112};
113
114Torrent :: KeyList
115Torrent :: buildKeyList( Group group )
116{
117    KeyList keys;
118
119    if( keys.empty( ) )
120        for( int i=0; i<PROPERTY_COUNT; ++i )
121            if( myProperties[i].id==ID || myProperties[i].group==group )
122                keys << myProperties[i].key;
123
124    return keys;
125}
126
127const Torrent :: KeyList&
128Torrent :: getInfoKeys( )
129{
130    static KeyList keys;
131    if( keys.isEmpty( ) )
132        keys << buildKeyList( INFO ) << "files";
133    return keys;
134}
135
136const Torrent :: KeyList&
137Torrent :: getStatKeys( )
138{
139    static KeyList keys( buildKeyList( STAT ) );
140    return keys;
141}
142
143const Torrent :: KeyList&
144Torrent :: getExtraStatKeys( )
145{
146    static KeyList keys;
147    if( keys.isEmpty( ) )
148        keys << buildKeyList( STAT_EXTRA ) << "fileStats";
149    return keys;
150}
151
152
153bool
154Torrent :: setInt( int i, int value )
155{
156    bool changed = false;
157
158    assert( 0<=i && i<PROPERTY_COUNT );
159    assert( myProperties[i].type == QVariant::Int );
160
161    if( myValues[i].isNull() || myValues[i].toInt()!=value )
162    {
163        myValues[i].setValue( value );
164        changed = true;
165    }
166
167    return changed;
168}
169
170bool
171Torrent :: setBool( int i, bool value )
172{
173    bool changed = false;
174
175    assert( 0<=i && i<PROPERTY_COUNT );
176    assert( myProperties[i].type == QVariant::Bool );
177
178    if( myValues[i].isNull() || myValues[i].toBool()!=value )
179    {
180        myValues[i].setValue( value );
181        changed = true;
182    }
183
184    return changed;
185}
186
187bool
188Torrent :: setDouble( int i, double value )
189{
190    bool changed = false;
191
192    assert( 0<=i && i<PROPERTY_COUNT );
193    assert( myProperties[i].type == QVariant::Double );
194
195    if( myValues[i].isNull() || myValues[i].toDouble()!=value )
196    {
197        myValues[i].setValue( value );
198        changed = true;
199    }
200
201    return changed;
202}
203
204bool
205Torrent :: setDateTime( int i, const QDateTime& value )
206{
207    bool changed = false;
208
209    assert( 0<=i && i<PROPERTY_COUNT );
210    assert( myProperties[i].type == QVariant::DateTime );
211
212    if( myValues[i].isNull() || myValues[i].toDateTime()!=value )
213    {
214        myValues[i].setValue( value );
215        changed = true;
216    }
217
218    return changed;
219}
220
221bool
222Torrent :: setSize( int i, qulonglong value )
223{
224    bool changed = false;
225
226    assert( 0<=i && i<PROPERTY_COUNT );
227    assert( myProperties[i].type == QVariant::ULongLong );
228
229    if( myValues[i].isNull() || myValues[i].toULongLong()!=value )
230    {
231        myValues[i].setValue( value );
232        changed = true;
233    }
234
235    return changed;
236}
237
238bool
239Torrent :: setString( int i, const char * value )
240{
241    bool changed = false;
242
243    assert( 0<=i && i<PROPERTY_COUNT );
244    assert( myProperties[i].type == QVariant::String );
245
246    if( myValues[i].isNull() || myValues[i].toString()!=value )
247    {
248        myValues[i].setValue( QString::fromUtf8( value ) );
249        changed = true;
250    }
251
252    return changed;
253}
254
255bool
256Torrent :: setIcon( int i, const QIcon& value )
257{
258    assert( 0<=i && i<PROPERTY_COUNT );
259    assert( myProperties[i].type == QVariant::Icon );
260
261    myValues[i].setValue( value );
262    return true;
263}
264
265int
266Torrent :: getInt( int i ) const
267{
268    assert( 0<=i && i<PROPERTY_COUNT );
269    assert( myProperties[i].type == QVariant::Int );
270
271    return myValues[i].toInt( );
272}
273
274QDateTime
275Torrent :: getDateTime( int i ) const
276{
277    assert( 0<=i && i<PROPERTY_COUNT );
278    assert( myProperties[i].type == QVariant::DateTime );
279
280    return myValues[i].toDateTime( );
281}
282
283bool
284Torrent :: getBool( int i ) const
285{
286    assert( 0<=i && i<PROPERTY_COUNT );
287    assert( myProperties[i].type == QVariant::Bool );
288
289    return myValues[i].toBool( );
290}
291
292qulonglong
293Torrent :: getSize( int i ) const
294{
295    assert( 0<=i && i<PROPERTY_COUNT );
296    assert( myProperties[i].type == QVariant::ULongLong );
297
298    return myValues[i].toULongLong( );
299}
300double
301Torrent :: getDouble( int i ) const
302{
303    assert( 0<=i && i<PROPERTY_COUNT );
304    assert( myProperties[i].type == QVariant::Double );
305
306    return myValues[i].toDouble( );
307}
308QString
309Torrent :: getString( int i ) const
310{
311    assert( 0<=i && i<PROPERTY_COUNT );
312    assert( myProperties[i].type == QVariant::String );
313
314    return myValues[i].toString( );
315}
316QIcon
317Torrent :: getIcon( int i ) const
318{
319    assert( 0<=i && i<PROPERTY_COUNT );
320    assert( myProperties[i].type == QVariant::Icon );
321
322    return myValues[i].value<QIcon>();
323}
324
325/***
326****
327***/
328
329bool
330Torrent :: getSeedRatio( double& ratio ) const
331{
332    bool isLimited;
333
334    switch( seedRatioMode( ) )
335    {
336        case TR_RATIOLIMIT_SINGLE:
337            isLimited = true;
338            ratio = seedRatioLimit( );
339            break;
340
341        case TR_RATIOLIMIT_GLOBAL:
342            if(( isLimited = myPrefs.getBool( Prefs :: RATIO_ENABLED )))
343                ratio = myPrefs.getDouble( Prefs :: RATIO );
344            break;
345
346        case TR_RATIOLIMIT_UNLIMITED:
347            isLimited = false;
348            break;
349    }
350
351    return isLimited;
352}
353
354bool
355Torrent :: hasFileSubstring( const QString& substr ) const
356{
357    foreach( const TrFile file, myFiles )
358        if( file.filename.contains( substr, Qt::CaseInsensitive ) )
359            return true;
360    return false;
361}
362
363bool
364Torrent :: hasTrackerSubstring( const QString& substr ) const
365{
366    foreach( QString s, myValues[TRACKERS].toStringList() )
367        if( s.contains( substr, Qt::CaseInsensitive ) )
368            return true;
369    return false;
370}
371
372int
373Torrent :: compareRatio( const Torrent& that ) const
374{
375    const double a = ratio( );
376    const double b = that.ratio( );
377    if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
378    if( (int)a == TR_RATIO_INF ) return 1;
379    if( (int)b == TR_RATIO_INF ) return -1;
380    if( a < b ) return -1;
381    if( a > b ) return 1;
382    return 0;
383}
384
385int
386Torrent :: compareETA( const Torrent& that ) const
387{
388    const bool haveA( hasETA( ) );
389    const bool haveB( that.hasETA( ) );
390    if( haveA && haveB ) return getETA() - that.getETA();
391    if( haveA ) return -1;
392    if( haveB ) return 1;
393    return 0;
394}
395
396int
397Torrent :: compareTracker( const Torrent& that ) const
398{
399    Q_UNUSED( that );
400
401    // FIXME
402    return 0;
403}
404
405/***
406****
407***/
408
409void
410Torrent :: updateMimeIcon( )
411{
412    const FileList& files( myFiles );
413
414    QIcon icon;
415
416    if( files.size( ) > 1 )
417        icon = QFileIconProvider().icon( QFileIconProvider::Folder );
418    else if( files.size( ) == 1 )
419        icon = Utils :: guessMimeIcon( files.at(0).filename );
420    else
421        icon = QIcon( );
422
423    setIcon( MIME_ICON, icon );
424}
425
426/***
427****
428***/
429
430void
431Torrent :: notifyComplete( ) const
432{
433    // if someone wants to implement notification, here's the hook.
434}
435
436/***
437****
438***/
439
440void
441Torrent :: update( tr_benc * d )
442{
443    bool changed = false;
444
445    for( int  i=0; i<PROPERTY_COUNT; ++i )
446    {
447        tr_benc * child = tr_bencDictFind( d, myProperties[i].key );
448        if( !child )
449            continue;
450
451        switch( myProperties[i].type )
452        {
453            case QVariant :: Int: {
454                int64_t val;
455                if( tr_bencGetInt( child, &val ) )
456                    changed |= setInt( i, val );
457                break;
458            }
459
460            case QVariant :: Bool: {
461                tr_bool val;
462                if( tr_bencGetBool( child, &val ) )
463                    changed |= setBool( i, val );
464                break;
465            }
466
467            case QVariant :: String: {
468                const char * val;
469                if( tr_bencGetStr( child, &val ) )
470                    changed |= setString( i, val );
471                break;
472            }
473
474            case QVariant :: ULongLong: {
475                int64_t val;
476                if( tr_bencGetInt( child, &val ) )
477                    changed |= setSize( i, val );
478                break;
479            }
480
481            case QVariant :: Double: {
482                double val;
483                if( tr_bencGetReal( child, &val ) )
484                    changed |= setDouble( i, val );
485                break;
486            }
487
488            case QVariant :: DateTime: {
489                int64_t val;
490                if( tr_bencGetInt( child, &val ) && val )
491                    changed |= setDateTime( i, QDateTime :: fromTime_t( val ) );
492                break;
493            }
494
495            case QVariant :: StringList:
496            case TrTypes :: PeerList:
497                break;
498
499            default:
500                assert( 0 && "unhandled type" );
501        }
502    }
503
504    tr_benc * files;
505
506    if( tr_bencDictFindList( d, "files", &files ) ) {
507        const char * str;
508        int64_t intVal;
509        int i = 0;
510        myFiles.clear( );
511        tr_benc * child;
512        while(( child = tr_bencListChild( files, i ))) {
513            TrFile file;
514            file.index = i++;
515            if( tr_bencDictFindStr( child, "name", &str ) )
516                file.filename = QString::fromUtf8( str );
517            if( tr_bencDictFindInt( child, "length", &intVal ) )
518                file.size = intVal;
519            myFiles.append( file );
520        }
521        updateMimeIcon( );
522        changed = true;
523    }
524
525    if( tr_bencDictFindList( d, "fileStats", &files ) ) {
526        const int n = tr_bencListSize( files );
527        assert( n == myFiles.size( ) );
528        for( int i=0; i<n; ++i ) {
529            int64_t intVal;
530            tr_bool boolVal;
531            tr_benc * child = tr_bencListChild( files, i );
532            TrFile& file( myFiles[i] );
533            if( tr_bencDictFindInt( child, "bytesCompleted", &intVal ) )
534                file.have = intVal;
535            if( tr_bencDictFindBool( child, "wanted", &boolVal ) )
536                file.wanted = boolVal;
537            if( tr_bencDictFindInt( child, "priority", &intVal ) )
538                file.priority = intVal;
539        }
540        changed = true;
541    }
542
543    tr_benc * trackers;
544    if( tr_bencDictFindList( d, "trackers", &trackers ) ) {
545        const char * str;
546        int i = 0;
547        QStringList list;
548        tr_benc * child;
549        while(( child = tr_bencListChild( trackers, i++ )))
550            if( tr_bencDictFindStr( child, "announce", &str ))
551                list.append( QString::fromUtf8( str ) );
552        if( myValues[TRACKERS] != list ) {
553            myValues[TRACKERS].setValue( list );
554            changed = true;
555        }
556    }
557
558    tr_benc * peers;
559    if( tr_bencDictFindList( d, "peers", &peers ) ) {
560        tr_benc * child;
561        PeerList peerList;
562        int childNum = 0;
563        while(( child = tr_bencListChild( peers, childNum++ ))) {
564            double d;
565            tr_bool b;
566            int64_t i;
567            const char * str;
568            Peer peer;
569            if( tr_bencDictFindStr( child, "address", &str ) )
570                peer.address = QString::fromUtf8( str );
571            if( tr_bencDictFindStr( child, "clientName", &str ) )
572                peer.clientName = QString::fromUtf8( str );
573            if( tr_bencDictFindBool( child, "clientIsChoked", &b ) )
574                peer.clientIsChoked = b;
575            if( tr_bencDictFindBool( child, "clientIsInterested", &b ) )
576                peer.clientIsInterested = b;
577            if( tr_bencDictFindBool( child, "isDownloadingFrom", &b ) )
578                peer.isDownloadingFrom = b;
579            if( tr_bencDictFindBool( child, "isEncrypted", &b ) )
580                peer.isEncrypted = b;
581            if( tr_bencDictFindBool( child, "isIncoming", &b ) )
582                peer.isIncoming = b;
583            if( tr_bencDictFindBool( child, "isUploadingTo", &b ) )
584                peer.isUploadingTo = b;
585            if( tr_bencDictFindBool( child, "peerIsChoked", &b ) )
586                peer.peerIsChoked = b;
587            if( tr_bencDictFindBool( child, "peerIsInterested", &b ) )
588                peer.peerIsInterested = b;
589            if( tr_bencDictFindInt( child, "port", &i ) )
590                peer.port = i;
591            if( tr_bencDictFindReal( child, "progress", &d ) )
592                peer.progress = d;
593            if( tr_bencDictFindInt( child, "rateToClient", &i ) )
594                peer.rateToClient = Speed::fromBps( i );
595            if( tr_bencDictFindInt( child, "rateToPeer", &i ) )
596                peer.rateToPeer = Speed::fromBps( i );
597            peerList << peer;
598        }
599        myValues[PEERS].setValue( peerList );
600        changed = true;
601    }
602
603    if( changed )
604        emit torrentChanged( id( ) );
605}
606
607QString
608Torrent :: activityString( ) const
609{
610    QString str;
611
612    switch( getActivity( ) )
613    {
614        case TR_STATUS_CHECK_WAIT: str = tr( "Waiting to verify local data" ); break;
615        case TR_STATUS_CHECK:      str = tr( "Verifying local data" ); break;
616        case TR_STATUS_DOWNLOAD:   str = tr( "Downloading" ); break;
617        case TR_STATUS_SEED:       str = tr( "Seeding" ); break;
618        case TR_STATUS_STOPPED:    str = tr( "Paused" ); break;
619    }
620
621    return str;
622}
623
624QString
625Torrent :: getError( ) const
626{
627    QString s = getString( ERROR_STRING );
628
629    switch( getInt( ERROR ) )
630    {
631        case TR_STAT_TRACKER_WARNING: s = tr( "Tracker gave a warning: %1" ).arg( s ); break;
632        case TR_STAT_TRACKER_ERROR: s = tr( "Tracker gave an error: %1" ).arg( s ); break;
633        case TR_STAT_LOCAL_ERROR: s = tr( "Error: %1" ).arg( s ); break;
634        default: s.clear(); break;
635    }
636
637    return s;
638}
Note: See TracBrowser for help on using the repository browser.