source: trunk/qt/session.cc @ 14225

Last change on this file since 14225 was 14225, checked in by jordan, 9 years ago

Licensing changes:

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