source: trunk/qt/session.cc @ 14395

Last change on this file since 14395 was 14395, checked in by mikedld, 8 years ago

#5543: Add a warning panel with id of duplicate torrent (patch by rb07 + personal touch)

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