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