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