source: trunk/qt/session.cc @ 14150

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

support qt5 in transmission-qt

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