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