source: trunk/qt/session.cc @ 13810

Last change on this file since 13810 was 13810, checked in by jordan, 8 years ago

(trunk) #1220 'change top folder names' -- add file-renaming to the Qt client

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