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