source: trunk/qt/session.cc @ 14394

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

Fix license wording in Qt client .cc files to match the rest of the code.

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