1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: session.cc 13448 2012-08-19 16:12:20Z jordan $
11 */
12
13#include <cassert>
14#include <iostream>
15
16#include <QApplication>
17#include <QByteArray>
18#include <QClipboard>
19#include <QCoreApplication>
20#include <QDesktopServices>
21#include <QMessageBox>
22#include <QNetworkProxy>
23#include <QNetworkProxyFactory>
24#include <QNetworkReply>
25#include <QNetworkRequest>
26#include <QSet>
27#include <QStringList>
28#include <QStyle>
29#include <QTextStream>
30
31#include <curl/curl.h>
32
33#include <event2/buffer.h>
34
35#include <libtransmission/transmission.h>
36#include <libtransmission/bencode.h>
37#include <libtransmission/json.h>
38#include <libtransmission/rpcimpl.h>
39#include <libtransmission/utils.h> // tr_free
40#include <libtransmission/version.h> // LONG_VERSION
41#include <libtransmission/web.h>
42
43#include "add-data.h"
44#include "prefs.h"
45#include "session.h"
46#include "session-dialog.h"
47#include "torrent.h"
48#include "utils.h"
49
50// #define DEBUG_HTTP
51
52namespace
53{
54    enum
55    {
56        TAG_SOME_TORRENTS,
57        TAG_ALL_TORRENTS,
58        TAG_SESSION_STATS,
59        TAG_SESSION_INFO,
60        TAG_BLOCKLIST_UPDATE,
61        TAG_ADD_TORRENT,
62        TAG_PORT_TEST,
63        TAG_MAGNET_LINK,
64
65        FIRST_UNIQUE_TAG
66    };
67}
68
69/***
70****
71***/
72
73namespace
74{
75    typedef Torrent::KeyList KeyList;
76    const KeyList& getInfoKeys( ) { return Torrent::getInfoKeys( ); }
77    const KeyList& getStatKeys( ) { return Torrent::getStatKeys( ); }
78    const KeyList& getExtraStatKeys( ) { return Torrent::getExtraStatKeys( ); }
79
80    void
81    addList( tr_benc * list, const KeyList& strings )
82    {
83        tr_bencListReserve( list, strings.size( ) );
84        foreach( const char * str, strings )
85            tr_bencListAddStr( list, str );
86    }
87}
88
89/***
90****
91***/
92
93void
94Session :: sessionSet( const char * key, const QVariant& value )
95{
96    tr_benc top;
97    tr_bencInitDict( &top, 2 );
98    tr_bencDictAddStr( &top, "method", "session-set" );
99    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 1 ) );
100    switch( value.type( ) ) {
101        case QVariant::Bool:   tr_bencDictAddBool ( args, key, value.toBool() ); break;
102        case QVariant::Int:    tr_bencDictAddInt  ( args, key, value.toInt() ); break;
103        case QVariant::Double: tr_bencDictAddReal ( args, key, value.toDouble() ); break;
104        case QVariant::String: tr_bencDictAddStr  ( args, key, value.toString().toUtf8().constData() ); break;
105        default: assert( "unknown type" );
106    }
107    exec( &top );
108    tr_bencFree( &top );
109}
110
111void
112Session :: portTest( )
113{
114    tr_benc top;
115    tr_bencInitDict( &top, 2 );
116    tr_bencDictAddStr( &top, "method", "port-test" );
117    tr_bencDictAddInt( &top, "tag", TAG_PORT_TEST );
118    exec( &top );
119    tr_bencFree( &top );
120}
121
122void
123Session :: copyMagnetLinkToClipboard( int torrentId )
124{
125    tr_benc top;
126    tr_bencInitDict( &top, 3 );
127    tr_bencDictAddStr( &top, "method", "torrent-get" );
128    tr_bencDictAddInt( &top, "tag", TAG_MAGNET_LINK );
129    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
130    tr_bencListAddInt( tr_bencDictAddList( args, "ids", 1 ), torrentId );
131    tr_bencListAddStr( tr_bencDictAddList( args, "fields", 1 ), "magnetLink" );
132
133    exec( &top );
134    tr_bencFree( &top );
135}
136
137void
138Session :: updatePref( int key )
139{
140    if( myPrefs.isCore( key ) ) switch( key )
141    {
142        case Prefs :: ALT_SPEED_LIMIT_DOWN:
143        case Prefs :: ALT_SPEED_LIMIT_ENABLED:
144        case Prefs :: ALT_SPEED_LIMIT_TIME_BEGIN:
145        case Prefs :: ALT_SPEED_LIMIT_TIME_DAY:
146        case Prefs :: ALT_SPEED_LIMIT_TIME_ENABLED:
147        case Prefs :: ALT_SPEED_LIMIT_TIME_END:
148        case Prefs :: ALT_SPEED_LIMIT_UP:
149        case Prefs :: BLOCKLIST_DATE:
150        case Prefs :: BLOCKLIST_ENABLED:
151        case Prefs :: BLOCKLIST_URL:
152        case Prefs :: DHT_ENABLED:
153        case Prefs :: DOWNLOAD_DIR:
154        case Prefs :: DOWNLOAD_QUEUE_ENABLED:
155        case Prefs :: DOWNLOAD_QUEUE_SIZE:
156        case Prefs :: DSPEED:
157        case Prefs :: DSPEED_ENABLED:
158        case Prefs :: IDLE_LIMIT:
159        case Prefs :: IDLE_LIMIT_ENABLED:
160        case Prefs :: INCOMPLETE_DIR:
161        case Prefs :: INCOMPLETE_DIR_ENABLED:
162        case Prefs :: LPD_ENABLED:
163        case Prefs :: PEER_LIMIT_GLOBAL:
164        case Prefs :: PEER_LIMIT_TORRENT:
165        case Prefs :: PEER_PORT:
166        case Prefs :: PEER_PORT_RANDOM_ON_START:
167        case Prefs :: QUEUE_STALLED_MINUTES:
168        case Prefs :: PEX_ENABLED:
169        case Prefs :: PORT_FORWARDING:
170        case Prefs :: SCRIPT_TORRENT_DONE_ENABLED:
171        case Prefs :: SCRIPT_TORRENT_DONE_FILENAME:
172        case Prefs :: START:
173        case Prefs :: TRASH_ORIGINAL:
174        case Prefs :: USPEED:
175        case Prefs :: USPEED_ENABLED:
176        case Prefs :: UTP_ENABLED:
177            sessionSet( myPrefs.keyStr(key), myPrefs.variant(key) );
178            break;
179
180        case Prefs :: RATIO:
181            sessionSet( "seedRatioLimit", myPrefs.variant(key) );
182            break;
183        case Prefs :: RATIO_ENABLED:
184            sessionSet( "seedRatioLimited", myPrefs.variant(key) );
185            break;
186
187        case Prefs :: ENCRYPTION:
188            {
189                const int i = myPrefs.variant(key).toInt();
190                switch( i )
191                {
192                    case 0:
193                        sessionSet( myPrefs.keyStr(key), "tolerated" );
194                        break;
195                    case 1:
196                        sessionSet( myPrefs.keyStr(key), "preferred" );
197                        break;
198                    case 2:
199                        sessionSet( myPrefs.keyStr(key), "required" );
200                        break;
201                }
202                break;
203            }
204
205        case Prefs :: RPC_AUTH_REQUIRED:
206            if( mySession )
207                tr_sessionSetRPCPasswordEnabled( mySession, myPrefs.getBool(key) );
208            break;
209        case Prefs :: RPC_ENABLED:
210            if( mySession )
211                tr_sessionSetRPCEnabled( mySession, myPrefs.getBool(key) );
212            break;
213        case Prefs :: RPC_PASSWORD:
214            if( mySession )
215                tr_sessionSetRPCPassword( mySession, myPrefs.getString(key).toUtf8().constData() );
216            break;
217        case Prefs :: RPC_PORT:
218            if( mySession )
219                tr_sessionSetRPCPort( mySession, myPrefs.getInt(key) );
220            break;
221        case Prefs :: RPC_USERNAME:
222            if( mySession )
223                tr_sessionSetRPCUsername( mySession, myPrefs.getString(key).toUtf8().constData() );
224            break;
225        case Prefs :: RPC_WHITELIST_ENABLED:
226            if( mySession )
227                tr_sessionSetRPCWhitelistEnabled( mySession, myPrefs.getBool(key) );
228            break;
229        case Prefs :: RPC_WHITELIST:
230            if( mySession )
231                tr_sessionSetRPCWhitelist( mySession, myPrefs.getString(key).toUtf8().constData() );
232            break;
233
234        default:
235            std::cerr << "unhandled pref: " << key << std::endl;
236    }
237}
238
239/***
240****
241***/
242
243Session :: Session( const char * configDir, Prefs& prefs ):
244    nextUniqueTag( FIRST_UNIQUE_TAG ),
245    myBlocklistSize( -1 ),
246    myPrefs( prefs ),
247    mySession( 0 ),
248    myConfigDir( QString::fromUtf8( configDir ) ),
249    myNAM( 0 )
250{
251    myStats.ratio = TR_RATIO_NA;
252    myStats.uploadedBytes = 0;
253    myStats.downloadedBytes = 0;
254    myStats.filesAdded = 0;
255    myStats.sessionCount = 0;
256    myStats.secondsActive = 0;
257    myCumulativeStats = myStats;
258
259    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
260}
261
262Session :: ~Session( )
263{
264    stop( );
265}
266
267QNetworkAccessManager *
268Session :: networkAccessManager( )
269{
270    if( myNAM == 0 )
271    {
272        myNAM = new QNetworkAccessManager;
273
274        connect( myNAM, SIGNAL(finished(QNetworkReply*)),
275                 this, SLOT(onFinished(QNetworkReply*)) );
276
277        connect( myNAM, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)),
278                 this, SIGNAL(httpAuthenticationRequired()) );
279    }
280
281    return myNAM;
282}
283
284/***
285****
286***/
287
288void
289Session :: stop( )
290{
291    if( myNAM != 0 )
292    {
293        myNAM->deleteLater( );
294        myNAM = 0;
295    }
296
297    myUrl.clear( );
298
299    if( mySession )
300    {
301        tr_sessionClose( mySession );
302        mySession = 0;
303    }
304}
305
306void
307Session :: restart( )
308{
309    stop( );
310    start( );
311}
312
313void
314Session :: start( )
315{
316    if( myPrefs.get<bool>(Prefs::SESSION_IS_REMOTE) )
317    {
318        QUrl url;
319        url.setScheme( "http" );
320        url.setHost( myPrefs.get<QString>(Prefs::SESSION_REMOTE_HOST) );
321        url.setPort( myPrefs.get<int>(Prefs::SESSION_REMOTE_PORT) );
322        url.setPath( "/transmission/rpc" );
323        if( myPrefs.get<bool>(Prefs::SESSION_REMOTE_AUTH) )
324        {
325            url.setUserName( myPrefs.get<QString>(Prefs::SESSION_REMOTE_USERNAME) );
326            url.setPassword( myPrefs.get<QString>(Prefs::SESSION_REMOTE_PASSWORD) );
327        }
328        myUrl = url;
329    }
330    else
331    {
332        tr_benc settings;
333        tr_bencInitDict( &settings, 0 );
334        tr_sessionLoadSettings( &settings, myConfigDir.toUtf8().constData(), "qt" );
335        mySession = tr_sessionInit( "qt", myConfigDir.toUtf8().constData(), true, &settings );
336        tr_bencFree( &settings );
337
338        tr_ctor * ctor = tr_ctorNew( mySession );
339        int torrentCount;
340        tr_torrent ** torrents = tr_sessionLoadTorrents( mySession, ctor, &torrentCount );
341        tr_free( torrents );
342        tr_ctorFree( ctor );
343    }
344
345    emit sourceChanged( );
346}
347
348bool
349Session :: isServer( ) const
350{
351    return mySession != 0;
352}
353
354bool
355Session :: isLocal( ) const
356{
357    if( mySession != 0 )
358        return true;
359
360    if( myUrl.host() == "127.0.0.1" )
361        return true;
362
363    if( !myUrl.host().compare( "localhost", Qt::CaseInsensitive ) )
364        return true;
365
366    return false;
367}
368
369/***
370****
371***/
372
373namespace
374{
375    tr_benc *
376    buildRequest( const char * method, tr_benc& top, int tag=-1 )
377    {
378        tr_bencInitDict( &top, 3 );
379        tr_bencDictAddStr( &top, "method", method );
380        if( tag >= 0 )
381            tr_bencDictAddInt( &top, "tag", tag );
382        return tr_bencDictAddDict( &top, "arguments", 0 );
383    }
384
385    void
386    addOptionalIds( tr_benc * args, const QSet<int>& ids )
387    {
388        if( !ids.isEmpty( ) )
389        {
390            tr_benc * idList( tr_bencDictAddList( args, "ids", ids.size( ) ) );
391            foreach( int i, ids )
392                tr_bencListAddInt( idList, i );
393        }
394    }
395}
396
397void
398Session :: torrentSet( const QSet<int>& ids, const QString& key, double value )
399{
400    tr_benc top;
401    tr_bencInitDict( &top, 2 );
402    tr_bencDictAddStr( &top, "method", "torrent-set" );
403    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
404    tr_bencDictAddReal( args, key.toUtf8().constData(), value );
405    addOptionalIds( args, ids );
406    exec( &top );
407    tr_bencFree( &top );
408}
409
410void
411Session :: torrentSet( const QSet<int>& ids, const QString& key, int value )
412{
413    tr_benc top;
414    tr_bencInitDict( &top, 2 );
415    tr_bencDictAddStr( &top, "method", "torrent-set" );
416    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
417    tr_bencDictAddInt( args, key.toUtf8().constData(), value );
418    addOptionalIds( args, ids );
419    exec( &top );
420    tr_bencFree( &top );
421}
422
423void
424Session :: torrentSet( const QSet<int>& ids, const QString& key, bool value )
425{
426    tr_benc top;
427    tr_bencInitDict( &top, 2 );
428    tr_bencDictAddStr( &top, "method", "torrent-set" );
429    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
430    tr_bencDictAddBool( args, key.toUtf8().constData(), value );
431    addOptionalIds( args, ids );
432    exec( &top );
433    tr_bencFree( &top );
434}
435
436void
437Session :: torrentSet( const QSet<int>& ids, const QString& key, const QStringList& value )
438{
439    tr_benc top;
440    tr_bencInitDict( &top, 2 );
441    tr_bencDictAddStr( &top, "method", "torrent-set" );
442    tr_benc * args = tr_bencDictAddDict( &top, "arguments", 2 );
443    addOptionalIds( args, ids );
444    tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
445    foreach( const QString str, value )
446        tr_bencListAddStr( list, str.toUtf8().constData() );
447    exec( &top );
448    tr_bencFree( &top );
449}
450
451void
452Session :: torrentSet( const QSet<int>& ids, const QString& key, const QList<int>& value )
453{
454    tr_benc top;
455    tr_bencInitDict( &top, 2 );
456    tr_bencDictAddStr( &top, "method", "torrent-set" );
457    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
458    addOptionalIds( args, ids );
459    tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), value.size( ) ) );
460    foreach( int i, value )
461        tr_bencListAddInt( list, i );
462    exec( &top );
463    tr_bencFree( &top );
464}
465
466void
467Session :: torrentSet( const QSet<int>& ids, const QString& key, const QPair<int,QString>& value )
468{
469    tr_benc top;
470    tr_bencInitDict( &top, 2 );
471    tr_bencDictAddStr( &top, "method", "torrent-set" );
472    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
473    addOptionalIds( args, ids );
474    tr_benc * list( tr_bencDictAddList( args, key.toUtf8().constData(), 2 ) );
475    tr_bencListAddInt( list, value.first );
476    tr_bencListAddStr( list, value.second.toUtf8().constData() );
477    exec( &top );
478    tr_bencFree( &top );
479}
480
481void
482Session :: torrentSetLocation( const QSet<int>& ids, const QString& location, bool doMove )
483{
484    tr_benc top;
485    tr_bencInitDict( &top, 2 );
486    tr_bencDictAddStr( &top, "method", "torrent-set-location" );
487    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 3 ) );
488    addOptionalIds( args, ids );
489    tr_bencDictAddStr( args, "location", location.toUtf8().constData() );
490    tr_bencDictAddBool( args, "move", doMove );
491    exec( &top );
492    tr_bencFree( &top );
493}
494
495void
496Session :: refreshTorrents( const QSet<int>& ids )
497{
498    if( ids.empty( ) )
499    {
500        refreshAllTorrents( );
501    }
502    else
503    {
504        tr_benc top;
505        tr_bencInitDict( &top, 3 );
506        tr_bencDictAddStr( &top, "method", "torrent-get" );
507        tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
508        tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
509        addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
510        addOptionalIds( args, ids );
511        exec( &top );
512        tr_bencFree( &top );
513    }
514}
515
516void
517Session :: refreshExtraStats( const QSet<int>& ids )
518{
519    tr_benc top;
520    tr_bencInitDict( &top, 3 );
521    tr_bencDictAddStr( &top, "method", "torrent-get" );
522    tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
523    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
524    addOptionalIds( args, ids );
525    addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) + getExtraStatKeys( ));
526    exec( &top );
527    tr_bencFree( &top );
528}
529
530void
531Session :: sendTorrentRequest( const char * request, const QSet<int>& ids )
532{
533    tr_benc top;
534    tr_benc * args( buildRequest( request, top ) );
535    addOptionalIds( args, ids );
536    exec( &top );
537    tr_bencFree( &top );
538
539    refreshTorrents( ids );
540}
541
542void Session :: pauseTorrents    ( const QSet<int>& ids ) { sendTorrentRequest( "torrent-stop", ids ); }
543void Session :: startTorrents    ( const QSet<int>& ids ) { sendTorrentRequest( "torrent-start", ids ); }
544void Session :: startTorrentsNow ( const QSet<int>& ids ) { sendTorrentRequest( "torrent-start-now", ids ); }
545void Session :: queueMoveTop     ( const QSet<int>& ids ) { sendTorrentRequest( "queue-move-top", ids ); }
546void Session :: queueMoveUp      ( const QSet<int>& ids ) { sendTorrentRequest( "queue-move-up", ids ); }
547void Session :: queueMoveDown    ( const QSet<int>& ids ) { sendTorrentRequest( "queue-move-down", ids ); }
548void Session :: queueMoveBottom  ( const QSet<int>& ids ) { sendTorrentRequest( "queue-move-bottom", ids ); }
549
550void
551Session :: refreshActiveTorrents( )
552{
553    tr_benc top;
554    tr_bencInitDict( &top, 3 );
555    tr_bencDictAddStr( &top, "method", "torrent-get" );
556    tr_bencDictAddInt( &top, "tag", TAG_SOME_TORRENTS );
557    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 2 ) );
558    tr_bencDictAddStr( args, "ids", "recently-active" );
559    addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
560    exec( &top );
561    tr_bencFree( &top );
562}
563
564void
565Session :: refreshAllTorrents( )
566{
567    tr_benc top;
568    tr_bencInitDict( &top, 3 );
569    tr_bencDictAddStr( &top, "method", "torrent-get" );
570    tr_bencDictAddInt( &top, "tag", TAG_ALL_TORRENTS );
571    tr_benc * args( tr_bencDictAddDict( &top, "arguments", 1 ) );
572    addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys( ) );
573    exec( &top );
574    tr_bencFree( &top );
575}
576
577void
578Session :: initTorrents( const QSet<int>& ids )
579{
580    tr_benc top;
581    const int tag( ids.isEmpty() ? TAG_ALL_TORRENTS : TAG_SOME_TORRENTS );
582    tr_benc * args( buildRequest( "torrent-get", top, tag ) );
583    addOptionalIds( args, ids );
584    addList( tr_bencDictAddList( args, "fields", 0 ), getStatKeys()+getInfoKeys() );
585    exec( &top );
586    tr_bencFree( &top );
587}
588
589void
590Session :: refreshSessionStats( )
591{
592    tr_benc top;
593    tr_bencInitDict( &top, 2 );
594    tr_bencDictAddStr( &top, "method", "session-stats" );
595    tr_bencDictAddInt( &top, "tag", TAG_SESSION_STATS );
596    exec( &top );
597    tr_bencFree( &top );
598}
599
600void
601Session :: refreshSessionInfo( )
602{
603    tr_benc top;
604    tr_bencInitDict( &top, 2 );
605    tr_bencDictAddStr( &top, "method", "session-get" );
606    tr_bencDictAddInt( &top, "tag", TAG_SESSION_INFO );
607    exec( &top );
608    tr_bencFree( &top );
609}
610
611void
612Session :: updateBlocklist( )
613{
614    tr_benc top;
615    tr_bencInitDict( &top, 2 );
616    tr_bencDictAddStr( &top, "method", "blocklist-update" );
617    tr_bencDictAddInt( &top, "tag", TAG_BLOCKLIST_UPDATE );
618    exec( &top );
619    tr_bencFree( &top );
620}
621
622/***
623****
624***/
625
626void
627Session :: exec( const tr_benc * request )
628{
629    char * str = tr_bencToStr( request, TR_FMT_JSON_LEAN, NULL );
630    exec( str );
631    tr_free( str );
632}
633
634void
635Session :: localSessionCallback( tr_session * session, struct evbuffer * json, void * self )
636{
637    Q_UNUSED( session );
638
639    ((Session*)self)->parseResponse( (const char*) evbuffer_pullup( json, -1 ), evbuffer_get_length( json ) );
640}
641
642#define REQUEST_DATA_PROPERTY_KEY "requestData"
643
644void
645Session :: exec( const char * json )
646{
647    if( mySession  )
648    {
649        tr_rpc_request_exec_json( mySession, json, strlen( json ), localSessionCallback, this );
650    }
651    else if( !myUrl.isEmpty( ) )
652    {
653        QNetworkRequest request;
654        request.setUrl( myUrl );
655        request.setRawHeader( "User-Agent", QString( QCoreApplication::instance()->applicationName() + "/" + LONG_VERSION_STRING ).toAscii() );
656        request.setRawHeader( "Content-Type", "application/json; charset=UTF-8" );
657        if( !mySessionId.isEmpty( ) )
658            request.setRawHeader( TR_RPC_SESSION_ID_HEADER, mySessionId.toAscii() );
659
660        const QByteArray requestData( json );
661        QNetworkReply * reply = networkAccessManager()->post( request, requestData );
662        reply->setProperty( REQUEST_DATA_PROPERTY_KEY, requestData );
663        connect( reply, SIGNAL(downloadProgress(qint64,qint64)), this, SIGNAL(dataReadProgress()));
664        connect( reply, SIGNAL(uploadProgress(qint64,qint64)), this, SIGNAL(dataSendProgress()));
665
666#ifdef DEBUG_HTTP
667        std::cerr << "sending " << "POST " << qPrintable( myUrl.path() ) << std::endl;
668        foreach( QByteArray b, request.rawHeaderList() )
669            std::cerr << b.constData()
670                      << ": "
671                      << request.rawHeader( b ).constData()
672                      << std::endl;
673        std::cerr << "Body:\n" << json << std::endl;
674#endif
675    }
676}
677
678void
679Session :: onFinished( QNetworkReply * reply )
680{
681#ifdef DEBUG_HTTP
682    std::cerr << "http response header: " << std::endl;
683    foreach( QByteArray b, reply->rawHeaderList() )
684        std::cerr << b.constData()
685                  << ": "
686                  << reply->rawHeader( b ).constData()
687                  << std::endl;
688    std::cerr << "json:\n" << reply->peek( reply->bytesAvailable() ).constData() << std::endl;
689#endif
690
691    if( ( reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() == 409 )
692        && ( reply->hasRawHeader( TR_RPC_SESSION_ID_HEADER ) ) )
693    {
694        // we got a 409 telling us our session id has expired.
695        // update it and resubmit the request.
696        mySessionId = QString( reply->rawHeader( TR_RPC_SESSION_ID_HEADER ) );
697        exec( reply->property( REQUEST_DATA_PROPERTY_KEY ).toByteArray( ).constData( ) );
698    }
699    else if( reply->error() != QNetworkReply::NoError )
700    {
701        std::cerr << "http error: " << qPrintable( reply->errorString() ) << std::endl;
702    }
703    else
704    {
705        const QByteArray response( reply->readAll() );
706        const char * json( response.constData( ) );
707        int jsonLength( response.size( ) );
708        if( jsonLength>0 && json[jsonLength-1] == '\n' ) --jsonLength;
709        parseResponse( json, jsonLength );
710    }
711
712    reply->deleteLater();
713}
714
715void
716Session :: parseResponse( const char * json, size_t jsonLength )
717{
718    tr_benc top;
719    const uint8_t * end( 0 );
720    const int err( tr_jsonParse( "rpc", json, jsonLength, &top, &end ) );
721    if( !err )
722    {
723        int64_t tag = -1;
724        const char * result = NULL;
725        tr_benc * args = NULL;
726
727        tr_bencDictFindInt ( &top, "tag", &tag );
728        tr_bencDictFindStr ( &top, "result", &result );
729        tr_bencDictFindDict( &top, "arguments", &args );
730
731        emit executed( tag, result, args );
732
733        tr_benc * torrents;
734        const char * str;
735
736        if( tr_bencDictFindInt( &top, "tag", &tag ) )
737        {
738            switch( tag )
739            {
740                case TAG_SOME_TORRENTS:
741                case TAG_ALL_TORRENTS:
742                    if( tr_bencDictFindDict( &top, "arguments", &args ) ) {
743                        if( tr_bencDictFindList( args, "torrents", &torrents ) )
744                            emit torrentsUpdated( torrents, tag==TAG_ALL_TORRENTS );
745                        if( tr_bencDictFindList( args, "removed", &torrents ) )
746                            emit torrentsRemoved( torrents );
747                    }
748                    break;
749
750                case TAG_SESSION_STATS:
751                    if( tr_bencDictFindDict( &top, "arguments", &args ) )
752                        updateStats( args );
753                    break;
754
755                case TAG_SESSION_INFO:
756                    if( tr_bencDictFindDict( &top, "arguments", &args ) )
757                        updateInfo( args );
758                    break;
759
760                case TAG_BLOCKLIST_UPDATE: {
761                    int64_t intVal = 0;
762                    if( tr_bencDictFindDict( &top, "arguments", &args ) )
763                        if( tr_bencDictFindInt( args, "blocklist-size", &intVal ) )
764                            setBlocklistSize( intVal );
765                    break;
766                }
767
768                case TAG_PORT_TEST: {
769                    bool isOpen = 0;
770                    if( tr_bencDictFindDict( &top, "arguments", &args ) )
771                        tr_bencDictFindBool( args, "port-is-open", &isOpen );
772                    emit portTested( (bool)isOpen );
773                }
774
775                case TAG_MAGNET_LINK: {
776                    tr_benc * args;
777                    tr_benc * torrents;
778                    tr_benc * child;
779                    const char * str;
780                    if( tr_bencDictFindDict( &top, "arguments", &args )
781                        && tr_bencDictFindList( args, "torrents", &torrents )
782                        && (( child = tr_bencListChild( torrents, 0 )))
783                        && tr_bencDictFindStr( child, "magnetLink", &str ) )
784                            QApplication::clipboard()->setText( str );
785                    break;
786                }
787
788                case TAG_ADD_TORRENT:
789                    str = "";
790                    if( tr_bencDictFindStr( &top, "result", &str ) && strcmp( str, "success" ) ) {
791                        QMessageBox * d = new QMessageBox( QMessageBox::Information,
792                                                           tr( "Add Torrent" ),
793                                                           QString::fromUtf8(str),
794                                                           QMessageBox::Close,
795                                                           QApplication::activeWindow());
796                        connect( d, SIGNAL(rejected()), d, SLOT(deleteLater()) );
797                        d->show( );
798                    }
799                    break;
800
801                default:
802                    break;
803            }
804        }
805        tr_bencFree( &top );
806    }
807}
808
809void
810Session :: updateStats( tr_benc * d, struct tr_session_stats * stats )
811{
812    int64_t i;
813
814    if( tr_bencDictFindInt( d, "uploadedBytes", &i ) )
815        stats->uploadedBytes = i;
816    if( tr_bencDictFindInt( d, "downloadedBytes", &i ) )
817        stats->downloadedBytes = i;
818    if( tr_bencDictFindInt( d, "filesAdded", &i ) )
819        stats->filesAdded = i;
820    if( tr_bencDictFindInt( d, "sessionCount", &i ) )
821        stats->sessionCount = i;
822    if( tr_bencDictFindInt( d, "secondsActive", &i ) )
823        stats->secondsActive = i;
824
825    stats->ratio = tr_getRatio( stats->uploadedBytes, stats->downloadedBytes );
826
827}
828
829void
830Session :: updateStats( tr_benc * d )
831{
832    tr_benc * c;
833
834    if( tr_bencDictFindDict( d, "current-stats", &c ) )
835        updateStats( c, &myStats );
836
837    if( tr_bencDictFindDict( d, "cumulative-stats", &c ) )
838        updateStats( c, &myCumulativeStats );
839
840    emit statsUpdated( );
841}
842
843void
844Session :: updateInfo( tr_benc * d )
845{
846    int64_t i;
847    const char * str;
848    disconnect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
849
850    for( int i=Prefs::FIRST_CORE_PREF; i<=Prefs::LAST_CORE_PREF; ++i )
851    {
852        const tr_benc * b( tr_bencDictFind( d, myPrefs.keyStr( i ) ) );
853
854        if( !b )
855            continue;
856
857        if( i == Prefs :: ENCRYPTION )
858        {
859            const char * val;
860            if( tr_bencGetStr( b, &val ) )
861            {
862                if( !qstrcmp( val , "required" ) )
863                    myPrefs.set( i, 2 );
864                else if( !qstrcmp( val , "preferred" ) )
865                    myPrefs.set( i, 1 );
866                else if( !qstrcmp( val , "tolerated" ) )
867                    myPrefs.set( i, 0 );
868            }
869            continue;
870        }
871
872        switch( myPrefs.type( i ) )
873        {
874            case QVariant :: Int: {
875                int64_t val;
876                if( tr_bencGetInt( b, &val ) )
877                    myPrefs.set( i, (int)val );
878                break;
879            }
880            case QVariant :: Double: {
881                double val;
882                if( tr_bencGetReal( b, &val ) )
883                    myPrefs.set( i, val );
884                break;
885            }
886            case QVariant :: Bool: {
887                bool val;
888                if( tr_bencGetBool( b, &val ) )
889                    myPrefs.set( i, (bool)val );
890                break;
891            }
892            case TrTypes :: FilterModeType:
893            case TrTypes :: SortModeType:
894            case QVariant :: String: {
895                const char * val;
896                if( tr_bencGetStr( b, &val ) )
897                    myPrefs.set( i, QString(val) );
898                break;
899            }
900            default:
901                break;
902        }
903    }
904
905    bool b;
906    double x;
907    if( tr_bencDictFindBool( d, "seedRatioLimited", &b ) )
908        myPrefs.set( Prefs::RATIO_ENABLED, b ? true : false );
909    if( tr_bencDictFindReal( d, "seedRatioLimit", &x ) )
910        myPrefs.set( Prefs::RATIO, x );
911
912    /* Use the C API to get settings that, for security reasons, aren't supported by RPC */
913    if( mySession != 0 )
914    {
915        myPrefs.set( Prefs::RPC_ENABLED,           tr_sessionIsRPCEnabled           ( mySession ) );
916        myPrefs.set( Prefs::RPC_AUTH_REQUIRED,     tr_sessionIsRPCPasswordEnabled   ( mySession ) );
917        myPrefs.set( Prefs::RPC_PASSWORD,          tr_sessionGetRPCPassword         ( mySession ) );
918        myPrefs.set( Prefs::RPC_PORT,              tr_sessionGetRPCPort             ( mySession ) );
919        myPrefs.set( Prefs::RPC_USERNAME,          tr_sessionGetRPCUsername         ( mySession ) );
920        myPrefs.set( Prefs::RPC_WHITELIST_ENABLED, tr_sessionGetRPCWhitelistEnabled ( mySession ) );
921        myPrefs.set( Prefs::RPC_WHITELIST,         tr_sessionGetRPCWhitelist        ( mySession ) );
922    }
923
924    if( tr_bencDictFindInt( d, "blocklist-size", &i ) && i!=blocklistSize( ) )
925        setBlocklistSize( i );
926
927    if( tr_bencDictFindStr( d, "version", &str ) && ( mySessionVersion != str ) )
928        mySessionVersion = str;
929
930    //std::cerr << "Session :: updateInfo end" << std::endl;
931    connect( &myPrefs, SIGNAL(changed(int)), this, SLOT(updatePref(int)) );
932
933    emit sessionUpdated( );
934}
935
936void
937Session :: setBlocklistSize( int64_t i )
938{
939    myBlocklistSize = i;
940
941    emit blocklistUpdated( i );
942}
943
944void
945Session :: addTorrent( const AddData& addMe )
946{
947    const QByteArray b64 = addMe.toBase64();
948
949    tr_benc top, *args;
950    tr_bencInitDict( &top, 2 );
951    tr_bencDictAddStr( &top, "method", "torrent-add" );
952    args = tr_bencDictAddDict( &top, "arguments", 2 );
953    tr_bencDictAddBool( args, "paused", !myPrefs.getBool( Prefs::START ) );
954    switch( addMe.type ) {
955        case AddData::MAGNET:   tr_bencDictAddStr( args, "filename", addMe.magnet.toUtf8().constData() ); break;
956        case AddData::URL:      tr_bencDictAddStr( args, "filename", addMe.url.toString().toUtf8().constData() ); break;
957        case AddData::FILENAME: /* fall-through */
958        case AddData::METAINFO: tr_bencDictAddRaw( args, "metainfo", b64.constData(), b64.size() ); break;
959        default: std::cerr << "Unhandled AddData type: " << addMe.type << std::endl;
960    }
961    exec( &top );
962    tr_bencFree( &top );
963}
964
965void
966Session :: addNewlyCreatedTorrent( const QString& filename, const QString& localPath )
967{
968    const QByteArray b64 = AddData(filename).toBase64();
969
970    tr_benc top, *args;
971    tr_bencInitDict( &top, 2 );
972    tr_bencDictAddStr( &top, "method", "torrent-add" );
973    args = tr_bencDictAddDict( &top, "arguments", 3 );
974    tr_bencDictAddStr( args, "download-dir", qPrintable(localPath) );
975    tr_bencDictAddBool( args, "paused", !myPrefs.getBool( Prefs::START ) );
976    tr_bencDictAddRaw( args, "metainfo", b64.constData(), b64.size() );
977    exec( &top );
978    tr_bencFree( &top );
979}
980
981void
982Session :: removeTorrents( const QSet<int>& ids, bool deleteFiles )
983{
984    if( !ids.isEmpty( ) )
985    {
986        tr_benc top, *args;
987        tr_bencInitDict( &top, 2 );
988        tr_bencDictAddStr( &top, "method", "torrent-remove" );
989        args = tr_bencDictAddDict( &top, "arguments", 2 );
990        addOptionalIds( args, ids );
991        tr_bencDictAddInt( args, "delete-local-data", deleteFiles );
992        exec( &top );
993        tr_bencFree( &top );
994    }
995}
996
997void
998Session :: verifyTorrents( const QSet<int>& ids )
999{
1000    if( !ids.isEmpty( ) )
1001    {
1002        tr_benc top, *args;
1003        tr_bencInitDict( &top, 2 );
1004        tr_bencDictAddStr( &top, "method", "torrent-verify" );
1005        args = tr_bencDictAddDict( &top, "arguments", 1 );
1006        addOptionalIds( args, ids );
1007        exec( &top );
1008        tr_bencFree( &top );
1009    }
1010}
1011
1012void
1013Session :: reannounceTorrents( const QSet<int>& ids )
1014{
1015    if( !ids.isEmpty( ) )
1016    {
1017        tr_benc top, *args;
1018        tr_bencInitDict( &top, 2 );
1019        tr_bencDictAddStr( &top, "method", "torrent-reannounce" );
1020        args = tr_bencDictAddDict( &top, "arguments", 1 );
1021        addOptionalIds( args, ids );
1022        exec( &top );
1023        tr_bencFree( &top );
1024    }
1025}
1026
1027/***
1028****
1029***/
1030
1031void
1032Session :: launchWebInterface( )
1033{
1034    QUrl url;
1035    if( !mySession ) // remote session
1036    {
1037        url = myUrl;
1038        url.setPath( "/transmission/web/" );
1039    }
1040    else // local session
1041    {
1042        url.setScheme( "http" );
1043        url.setHost( "localhost" );
1044        url.setPort( myPrefs.getInt( Prefs::RPC_PORT ) );
1045    }
1046    QDesktopServices :: openUrl( url );
1047}
1048