source: trunk/qt/session.cc @ 13883

Last change on this file since 13883 was 13883, checked in by jordan, 10 years ago

(qt) #4076 'free space indicator' -- added.

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