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