source: trunk/qt/app.cc @ 14379

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

Fix various cppcheck comments for Qt client code

  • Property svn:keywords set to Date Rev Author Id
File size: 16.7 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: app.cc 14379 2014-12-12 23:52:17Z mikedld $
8 */
9
10#include <cassert>
11#include <ctime>
12#include <iostream>
13
14#include <QDBusConnection>
15#include <QDBusConnectionInterface>
16#include <QDBusError>
17#include <QDBusMessage>
18#include <QDialogButtonBox>
19#include <QIcon>
20#include <QLabel>
21#include <QLibraryInfo>
22#include <QProcess>
23#include <QRect>
24
25#include <libtransmission/transmission.h>
26#include <libtransmission/tr-getopt.h>
27#include <libtransmission/utils.h>
28#include <libtransmission/version.h>
29
30#include "add-data.h"
31#include "app.h"
32#include "dbus-adaptor.h"
33#include "formatter.h"
34#include "mainwin.h"
35#include "options.h"
36#include "prefs.h"
37#include "session.h"
38#include "session-dialog.h"
39#include "torrent-model.h"
40#include "utils.h"
41#include "watchdir.h"
42
43namespace
44{
45  const QString DBUS_SERVICE     = QString::fromUtf8 ("com.transmissionbt.Transmission" );
46  const QString DBUS_OBJECT_PATH = QString::fromUtf8 ("/com/transmissionbt/Transmission");
47  const QString DBUS_INTERFACE   = QString::fromUtf8 ("com.transmissionbt.Transmission" );
48
49  const char * MY_READABLE_NAME ("transmission-qt");
50
51  const tr_option opts[] =
52  {
53    { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
54    { 'm', "minimized",  "Start minimized in system tray", "m", 0, NULL },
55    { 'p', "port",  "Port to use when connecting to an existing session", "p", 1, "<port>" },
56    { 'r', "remote",  "Connect to an existing session at the specified hostname", "r", 1, "<host>" },
57    { 'u', "username", "Username to use when connecting to an existing session", "u", 1, "<username>" },
58    { 'v', "version", "Show version number and exit", "v", 0, NULL },
59    { 'w', "password", "Password to use when connecting to an existing session", "w", 1, "<password>" },
60    { 0, NULL, NULL, NULL, 0, NULL }
61  };
62
63  const char*
64  getUsage (void)
65  {
66    return "Usage:\n"
67           "  transmission [OPTIONS...] [torrent files]";
68  }
69
70  void
71  showUsage (void)
72  {
73    tr_getopt_usage (MY_READABLE_NAME, getUsage (), opts);
74    exit (0);
75  }
76
77  enum
78  {
79    STATS_REFRESH_INTERVAL_MSEC   = 3000,
80    SESSION_REFRESH_INTERVAL_MSEC = 3000,
81    MODEL_REFRESH_INTERVAL_MSEC   = 3000
82  };
83}
84
85MyApp::MyApp (int& argc, char ** argv):
86  QApplication (argc, argv),
87  myPrefs(nullptr),
88  mySession(nullptr),
89  myModel(nullptr),
90  myWindow(nullptr),
91  myWatchDir(nullptr),
92  myLastFullUpdateTime (0)
93{
94  const QString MY_CONFIG_NAME = QString::fromUtf8 ("transmission");
95
96  setApplicationName (MY_CONFIG_NAME);
97
98  // install the qt translator
99  qtTranslator.load ("qt_" + QLocale::system ().name (), QLibraryInfo::location (QLibraryInfo::TranslationsPath));
100  installTranslator (&qtTranslator);
101
102  // install the transmission translator
103  appTranslator.load (QString (MY_CONFIG_NAME) + "_" + QLocale::system ().name (), QCoreApplication::applicationDirPath () + "/translations");
104  installTranslator (&appTranslator);
105
106  Formatter::initUnits ();
107
108  // set the default icon
109  QIcon icon = QIcon::fromTheme ("transmission");
110  if (icon.isNull ())
111    {
112      QList<int> sizes;
113      sizes << 16 << 22 << 24 << 32 << 48 << 64 << 72 << 96 << 128 << 192 << 256;
114      foreach (int size, sizes)
115        icon.addPixmap (QPixmap (QString::fromUtf8 (":/icons/transmission-%1.png").arg (size)));
116    }
117  setWindowIcon (icon);
118
119  // parse the command-line arguments
120  int c;
121  bool minimized = false;
122  const char * optarg;
123  const char * host = 0;
124  const char * port = 0;
125  const char * username = 0;
126  const char * password = 0;
127  const char * configDir = 0;
128  QStringList filenames;
129  while ((c = tr_getopt (getUsage(), argc, (const char**)argv, opts, &optarg)))
130    {
131      switch (c)
132        {
133          case 'g': configDir = optarg; break;
134          case 'p': port = optarg; break;
135          case 'r': host = optarg; break;
136          case 'u': username = optarg; break;
137          case 'w': password = optarg; break;
138          case 'm': minimized = true; break;
139          case 'v': std::cerr << MY_READABLE_NAME << ' ' << LONG_VERSION_STRING << std::endl; ::exit (0); break;
140          case TR_OPT_ERR: Utils::toStderr (QObject::tr ("Invalid option")); showUsage (); break;
141          default:         filenames.append (optarg); break;
142        }
143    }
144
145  // try to delegate the work to an existing copy of Transmission
146  // before starting ourselves...
147  QDBusConnection bus = QDBusConnection::sessionBus ();
148  if (bus.isConnected ())
149  {
150    bool delegated = false;
151    for (int i=0, n=filenames.size (); i<n; ++i)
152      {
153        QDBusMessage request = QDBusMessage::createMethodCall (DBUS_SERVICE,
154                                                               DBUS_OBJECT_PATH,
155                                                               DBUS_INTERFACE,
156                                                               QString::fromUtf8 ("AddMetainfo"));
157        QList<QVariant> arguments;
158        AddData a (filenames[i]);
159        switch (a.type)
160          {
161            case AddData::URL:      arguments.push_back (a.url.toString ()); break;
162            case AddData::MAGNET:   arguments.push_back (a.magnet); break;
163            case AddData::FILENAME: arguments.push_back (a.toBase64 ().constData ()); break;
164            case AddData::METAINFO: arguments.push_back (a.toBase64 ().constData ()); break;
165            default:                break;
166          }
167        request.setArguments (arguments);
168
169        QDBusMessage response = bus.call (request);
170        //std::cerr << qPrintable (response.errorName ()) << std::endl;
171        //std::cerr << qPrintable (response.errorMessage ()) << std::endl;
172        arguments = response.arguments ();
173        delegated |= (arguments.size ()==1) && arguments[0].toBool ();
174      }
175
176    if (delegated)
177      {
178        QTimer::singleShot (0, this, SLOT (quit ()));
179        return;
180      }
181  }
182
183  // set the fallback config dir
184  if (configDir == 0)
185    configDir = tr_getDefaultConfigDir ("transmission");
186
187  // ensure our config directory exists
188  QDir dir (configDir);
189  if (!dir.exists ())
190    dir.mkpath (configDir);
191
192  // is this the first time we've run transmission?
193  const bool firstTime = !QFile (QDir (configDir).absoluteFilePath ("settings.json")).exists ();
194
195  // initialize the prefs
196  myPrefs = new Prefs (configDir);
197  if (host != 0)
198    myPrefs->set (Prefs::SESSION_REMOTE_HOST, host);
199  if (port != 0)
200    myPrefs->set (Prefs::SESSION_REMOTE_PORT, port);
201  if (username != 0)
202    myPrefs->set (Prefs::SESSION_REMOTE_USERNAME, username);
203  if (password != 0)
204    myPrefs->set (Prefs::SESSION_REMOTE_PASSWORD, password);
205  if ((host != 0) || (port != 0) || (username != 0) || (password != 0))
206    myPrefs->set (Prefs::SESSION_IS_REMOTE, true);
207  if (myPrefs->getBool (Prefs::START_MINIMIZED))
208    minimized = true;
209
210  // start as minimized only if the system tray present
211  if (!myPrefs->getBool (Prefs::SHOW_TRAY_ICON))
212    minimized = false;
213
214  mySession = new Session (configDir, *myPrefs);
215  myModel = new TorrentModel (*myPrefs);
216  myWindow = new TrMainWindow (*mySession, *myPrefs, *myModel, minimized);
217  myWatchDir = new WatchDir (*myModel);
218
219  // when the session gets torrent info, update the model
220  connect (mySession, SIGNAL (torrentsUpdated (tr_variant*,bool)), myModel, SLOT (updateTorrents (tr_variant*,bool)));
221  connect (mySession, SIGNAL (torrentsUpdated (tr_variant*,bool)), myWindow, SLOT (refreshActionSensitivity ()));
222  connect (mySession, SIGNAL (torrentsRemoved (tr_variant*)), myModel, SLOT (removeTorrents (tr_variant*)));
223  // when the session source gets changed, request a full refresh
224  connect (mySession, SIGNAL (sourceChanged ()), this, SLOT (onSessionSourceChanged ()));
225  // when the model sees a torrent for the first time, ask the session for full info on it
226  connect (myModel, SIGNAL (torrentsAdded (QSet<int>)), mySession, SLOT (initTorrents (QSet<int>)));
227  connect (myModel, SIGNAL (torrentsAdded (QSet<int>)), this, SLOT (onTorrentsAdded (QSet<int>)));
228
229  mySession->initTorrents ();
230  mySession->refreshSessionStats ();
231
232  // when torrents are added to the watch directory, tell the session
233  connect (myWatchDir, SIGNAL (torrentFileAdded (QString)), this, SLOT (addTorrent (QString)));
234
235  // init from preferences
236  QList<int> initKeys;
237  initKeys << Prefs::DIR_WATCH;
238  foreach (int key, initKeys)
239    refreshPref (key);
240  connect (myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (const int)));
241
242  QTimer * timer = &myModelTimer;
243  connect (timer, SIGNAL (timeout ()), this, SLOT (refreshTorrents ()));
244  timer->setSingleShot (false);
245  timer->setInterval (MODEL_REFRESH_INTERVAL_MSEC);
246  timer->start ();
247
248  timer = &myStatsTimer;
249  connect (timer, SIGNAL (timeout ()), mySession, SLOT (refreshSessionStats ()));
250  timer->setSingleShot (false);
251  timer->setInterval (STATS_REFRESH_INTERVAL_MSEC);
252  timer->start ();
253
254  timer = &mySessionTimer;
255  connect (timer, SIGNAL (timeout ()), mySession, SLOT (refreshSessionInfo ()));
256  timer->setSingleShot (false);
257  timer->setInterval (SESSION_REFRESH_INTERVAL_MSEC);
258  timer->start ();
259
260  maybeUpdateBlocklist ();
261
262  if (!firstTime)
263    {
264      mySession->restart ();
265    }
266  else
267    {
268      QDialog * d = new SessionDialog (*mySession, *myPrefs, myWindow);
269      d->show ();
270    }
271
272  if (!myPrefs->getBool (Prefs::USER_HAS_GIVEN_INFORMED_CONSENT))
273    {
274      QDialog * dialog = new QDialog (myWindow);
275      dialog->setModal (true);
276      QVBoxLayout * v = new QVBoxLayout (dialog);
277      QLabel * l = new QLabel (tr ("Transmission is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility."));
278      l->setWordWrap (true);
279      v->addWidget (l);
280      QDialogButtonBox * box = new QDialogButtonBox;
281      box->addButton (new QPushButton (tr ("&Cancel")), QDialogButtonBox::RejectRole);
282      QPushButton * agree = new QPushButton (tr ("I &Agree"));
283      agree->setDefault (true);
284      box->addButton (agree, QDialogButtonBox::AcceptRole);
285      box->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Fixed);
286      box->setOrientation (Qt::Horizontal);
287      v->addWidget (box);
288      connect (box, SIGNAL (rejected ()), this, SLOT (quit ()));
289      connect (box, SIGNAL (accepted ()), dialog, SLOT (deleteLater ()));
290      connect (box, SIGNAL (accepted ()), this, SLOT (consentGiven ()));
291      dialog->show ();
292    }
293
294  for (QStringList::const_iterator it=filenames.begin (), end=filenames.end (); it!=end; ++it)
295    addTorrent (*it);
296
297  // register as the dbus handler for Transmission
298  if (bus.isConnected ())
299    {
300      new TrDBusAdaptor (this);
301      if (!bus.registerService (DBUS_SERVICE))
302        std::cerr << "couldn't register " << qPrintable (DBUS_SERVICE) << std::endl;
303      if (!bus.registerObject (DBUS_OBJECT_PATH, this))
304        std::cerr << "couldn't register " << qPrintable (DBUS_OBJECT_PATH) << std::endl;
305    }
306}
307
308/* these functions are for popping up desktop notifications */
309
310void
311MyApp::onTorrentsAdded (const QSet<int>& torrents)
312{
313  if (!myPrefs->getBool (Prefs::SHOW_NOTIFICATION_ON_ADD))
314    return;
315
316  foreach (int id, torrents)
317    {
318      Torrent * tor = myModel->getTorrentFromId (id);
319
320      if (tor->name ().isEmpty ()) // wait until the torrent's INFO fields are loaded
321        {
322          connect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onNewTorrentChanged (int)));
323        }
324      else
325        {
326          onNewTorrentChanged (id);
327
328          if (!tor->isSeed ())
329            connect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
330        }
331    }
332}
333
334void
335MyApp::onTorrentCompleted (int id)
336{
337  Torrent * tor = myModel->getTorrentFromId (id);
338
339  if (tor)
340    {
341      if (myPrefs->getBool (Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
342        notifyApp (tr ("Torrent Completed"), tor->name ());
343
344      if (myPrefs->getBool (Prefs::COMPLETE_SOUND_ENABLED))
345        {
346#if defined (Q_OS_WIN) || defined (Q_OS_MAC)
347          QApplication::beep ();
348#else
349          QProcess::execute (myPrefs->getString (Prefs::COMPLETE_SOUND_COMMAND));
350#endif
351        }
352
353      disconnect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
354    }
355}
356
357void
358MyApp::onNewTorrentChanged (int id)
359{
360  Torrent * tor = myModel->getTorrentFromId (id);
361
362  if (tor && !tor->name ().isEmpty ())
363    {
364      const int age_secs = tor->dateAdded ().secsTo (QDateTime::currentDateTime ());
365      if (age_secs < 30)
366        notifyApp (tr ("Torrent Added"), tor->name ());
367
368      disconnect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onNewTorrentChanged (int)));
369
370      if (!tor->isSeed ())
371        connect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
372    }
373}
374
375/***
376****
377***/
378
379void
380MyApp::consentGiven ()
381{
382  myPrefs->set<bool> (Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true);
383}
384
385MyApp::~MyApp ()
386{
387  if (myPrefs != nullptr && myWindow != nullptr)
388    {
389      const QRect mainwinRect (myWindow->geometry ());
390      myPrefs->set (Prefs::MAIN_WINDOW_HEIGHT, std::max (100, mainwinRect.height ()));
391      myPrefs->set (Prefs::MAIN_WINDOW_WIDTH, std::max (100, mainwinRect.width ()));
392      myPrefs->set (Prefs::MAIN_WINDOW_X, mainwinRect.x ());
393      myPrefs->set (Prefs::MAIN_WINDOW_Y, mainwinRect.y ());
394    }
395
396  delete myWatchDir;
397  delete myWindow;
398  delete myModel;
399  delete mySession;
400  delete myPrefs;
401}
402
403/***
404****
405***/
406
407void
408MyApp::refreshPref (int key)
409{
410  switch (key)
411    {
412      case Prefs::BLOCKLIST_UPDATES_ENABLED:
413        maybeUpdateBlocklist ();
414        break;
415
416      case Prefs::DIR_WATCH:
417      case Prefs::DIR_WATCH_ENABLED:
418        {
419          const QString path (myPrefs->getString (Prefs::DIR_WATCH));
420          const bool isEnabled (myPrefs->getBool (Prefs::DIR_WATCH_ENABLED));
421          myWatchDir->setPath (path, isEnabled);
422          break;
423        }
424
425      default:
426        break;
427    }
428}
429
430void
431MyApp::maybeUpdateBlocklist ()
432{
433  if (!myPrefs->getBool (Prefs::BLOCKLIST_UPDATES_ENABLED))
434    return;
435
436  const QDateTime lastUpdatedAt = myPrefs->getDateTime (Prefs::BLOCKLIST_DATE);
437  const QDateTime nextUpdateAt = lastUpdatedAt.addDays (7);
438  const QDateTime now = QDateTime::currentDateTime ();
439
440  if (now < nextUpdateAt)
441    {
442      mySession->updateBlocklist ();
443      myPrefs->set (Prefs::BLOCKLIST_DATE, now);
444    }
445}
446
447void
448MyApp::onSessionSourceChanged ()
449{
450  mySession->initTorrents ();
451  mySession->refreshSessionStats ();
452  mySession->refreshSessionInfo ();
453}
454
455void
456MyApp::refreshTorrents ()
457{
458  // usually we just poll the torrents that have shown recent activity,
459  // but we also periodically ask for updates on the others to ensure
460  // nothing's falling through the cracks.
461  const time_t now = time (NULL);
462  if (myLastFullUpdateTime + 60 >= now)
463    {
464      mySession->refreshActiveTorrents ();
465    }
466  else
467    {
468      myLastFullUpdateTime = now;
469      mySession->refreshAllTorrents ();
470    }
471}
472
473/***
474****
475***/
476
477void
478MyApp::addTorrent (const QString& key)
479{
480  const AddData addme (key);
481
482  if (addme.type != addme.NONE)
483    addTorrent (addme);
484}
485
486void
487MyApp::addTorrent (const AddData& addme)
488{
489  if (!myPrefs->getBool (Prefs::OPTIONS_PROMPT))
490    {
491      mySession->addTorrent (addme);
492    }
493  else
494    {
495      Options * o = new Options (*mySession, *myPrefs, addme, myWindow);
496      o->show ();
497    }
498
499  raise ();
500}
501
502/***
503****
504***/
505
506void
507MyApp::raise ()
508{
509  QApplication::alert (myWindow);
510}
511
512bool
513MyApp::notifyApp (const QString& title, const QString& body) const
514{
515  const QString dbusServiceName   = QString::fromUtf8 ("org.freedesktop.Notifications");
516  const QString dbusInterfaceName = QString::fromUtf8 ("org.freedesktop.Notifications");
517  const QString dbusPath          = QString::fromUtf8 ("/org/freedesktop/Notifications");
518
519  QDBusMessage m = QDBusMessage::createMethodCall (dbusServiceName, dbusPath, dbusInterfaceName, QString::fromUtf8 ("Notify"));
520  QList<QVariant> args;
521  args.append (QString::fromUtf8 ("Transmission")); // app_name
522  args.append (0U);                                   // replaces_id
523  args.append (QString::fromUtf8 ("transmission")); // icon
524  args.append (title);                                // summary
525  args.append (body);                                 // body
526  args.append (QStringList ());                       // actions - unused for plain passive popups
527  args.append (QVariantMap ());                       // hints - unused atm
528  args.append (int32_t (-1));                          // use the default timeout period
529  m.setArguments (args);
530  QDBusMessage replyMsg = QDBusConnection::sessionBus ().call (m);
531  //std::cerr << qPrintable (replyMsg.errorName ()) << std::endl;
532  //std::cerr << qPrintable (replyMsg.errorMessage ()) << std::endl;
533  return (replyMsg.type () == QDBusMessage::ReplyMessage) && !replyMsg.arguments ().isEmpty ();
534}
535
536/***
537****
538***/
539
540int
541main (int argc, char * argv[])
542{
543#ifdef _WIN32
544  tr_win32_make_args_utf8 (&argc, &argv);
545#endif
546
547  MyApp app (argc, argv);
548  return app.exec ();
549}
Note: See TracBrowser for help on using the repository browser.