Transmission: update to 2.82
[tomato.git] / release / src / router / transmission / qt / app.cc
blob92b882b2803bf3e02cb2e68da831a02132462205
1 /*
2 * This file Copyright (C) Mnemosyne LLC
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.
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10 * $Id: app.cc 14150 2013-07-27 21:58:14Z jordan $
13 #include <cassert>
14 #include <ctime>
15 #include <iostream>
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>
28 #include <libtransmission/transmission.h>
29 #include <libtransmission/tr-getopt.h>
30 #include <libtransmission/utils.h>
31 #include <libtransmission/version.h>
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"
46 namespace
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" );
52 const char * MY_READABLE_NAME ("transmission-qt");
54 const tr_option opts[] =
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 }
66 const char*
67 getUsage (void)
69 return "Usage:\n"
70 " transmission [OPTIONS...] [torrent files]";
73 void
74 showUsage (void)
76 tr_getopt_usage (MY_READABLE_NAME, getUsage (), opts);
77 exit (0);
80 enum
82 STATS_REFRESH_INTERVAL_MSEC = 3000,
83 SESSION_REFRESH_INTERVAL_MSEC = 3000,
84 MODEL_REFRESH_INTERVAL_MSEC = 3000
88 MyApp :: MyApp (int& argc, char ** argv):
89 QApplication (argc, argv),
90 myLastFullUpdateTime (0)
92 const QString MY_CONFIG_NAME = QString::fromUtf8 ("transmission");
94 setApplicationName (MY_CONFIG_NAME);
96 // install the qt translator
97 qtTranslator.load ("qt_" + QLocale::system ().name (), QLibraryInfo::location (QLibraryInfo::TranslationsPath));
98 installTranslator (&qtTranslator);
100 // install the transmission translator
101 appTranslator.load (QString (MY_CONFIG_NAME) + "_" + QLocale::system ().name (), QCoreApplication::applicationDirPath () + "/translations");
102 installTranslator (&appTranslator);
104 Formatter::initUnits ();
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);
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)))
126 switch (c)
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;
140 // set the fallback config dir
141 if (configDir == 0)
142 configDir = tr_getDefaultConfigDir ("transmission");
144 // ensure our config directory exists
145 QDir dir (configDir);
146 if (!dir.exists ())
147 dir.mkpath (configDir);
149 // is this the first time we've run transmission?
150 const bool firstTime = !QFile (QDir (configDir).absoluteFilePath ("settings.json")).exists ();
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;
167 // start as minimized only if the system tray present
168 if (!myPrefs->getBool (Prefs::SHOW_TRAY_ICON))
169 minimized = false;
171 mySession = new Session (configDir, *myPrefs);
172 myModel = new TorrentModel (*myPrefs);
173 myWindow = new TrMainWindow (*mySession, *myPrefs, *myModel, minimized);
174 myWatchDir = new WatchDir (*myModel);
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>)));
186 mySession->initTorrents ();
187 mySession->refreshSessionStats ();
189 // when torrents are added to the watch directory, tell the session
190 connect (myWatchDir, SIGNAL (torrentFileAdded (QString)), this, SLOT (addTorrent (QString)));
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)));
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 ();
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 ();
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 ();
217 maybeUpdateBlocklist ();
219 if (!firstTime)
221 mySession->restart ();
223 else
225 QDialog * d = new SessionDialog (*mySession, *myPrefs, myWindow);
226 d->show ();
229 if (!myPrefs->getBool (Prefs::USER_HAS_GIVEN_INFORMED_CONSENT))
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 ();
251 for (QStringList::const_iterator it=filenames.begin (), end=filenames.end (); it!=end; ++it)
252 addTorrent (*it);
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;
263 /* these functions are for popping up desktop notifications */
265 void
266 MyApp :: onTorrentsAdded (QSet<int> torrents)
268 if (!myPrefs->getBool (Prefs::SHOW_NOTIFICATION_ON_ADD))
269 return;
271 foreach (int id, torrents)
273 Torrent * tor = myModel->getTorrentFromId (id);
275 if (tor->name ().isEmpty ()) // wait until the torrent's INFO fields are loaded
277 connect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onNewTorrentChanged (int)));
279 else
281 onNewTorrentChanged (id);
283 if (!tor->isSeed ())
284 connect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
289 void
290 MyApp :: onTorrentCompleted (int id)
292 Torrent * tor = myModel->getTorrentFromId (id);
294 if (tor)
296 if (myPrefs->getBool (Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
297 notify (tr ("Torrent Completed"), tor->name ());
299 if (myPrefs->getBool (Prefs::COMPLETE_SOUND_ENABLED))
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
308 disconnect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
312 void
313 MyApp :: onNewTorrentChanged (int id)
315 Torrent * tor = myModel->getTorrentFromId (id);
317 if (tor && !tor->name ().isEmpty ())
319 const int age_secs = tor->dateAdded ().secsTo (QDateTime::currentDateTime ());
320 if (age_secs < 30)
321 notify (tr ("Torrent Added"), tor->name ());
323 disconnect (tor, SIGNAL (torrentChanged (int)), this, SLOT (onNewTorrentChanged (int)));
325 if (!tor->isSeed ())
326 connect (tor, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
330 /***
331 ****
332 ***/
334 void
335 MyApp :: consentGiven ()
337 myPrefs->set<bool> (Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true);
340 MyApp :: ~MyApp ()
342 const QRect mainwinRect (myWindow->geometry ());
343 delete myWatchDir;
344 delete myWindow;
345 delete myModel;
346 delete mySession;
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;
355 /***
356 ****
357 ***/
359 void
360 MyApp :: refreshPref (int key)
362 switch (key)
364 case Prefs :: BLOCKLIST_UPDATES_ENABLED:
365 maybeUpdateBlocklist ();
366 break;
368 case Prefs :: DIR_WATCH:
369 case Prefs :: DIR_WATCH_ENABLED:
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;
377 default:
378 break;
382 void
383 MyApp :: maybeUpdateBlocklist ()
385 if (!myPrefs->getBool (Prefs :: BLOCKLIST_UPDATES_ENABLED))
386 return;
388 const QDateTime lastUpdatedAt = myPrefs->getDateTime (Prefs :: BLOCKLIST_DATE);
389 const QDateTime nextUpdateAt = lastUpdatedAt.addDays (7);
390 const QDateTime now = QDateTime::currentDateTime ();
392 if (now < nextUpdateAt)
394 mySession->updateBlocklist ();
395 myPrefs->set (Prefs :: BLOCKLIST_DATE, now);
399 void
400 MyApp :: onSessionSourceChanged ()
402 mySession->initTorrents ();
403 mySession->refreshSessionStats ();
404 mySession->refreshSessionInfo ();
407 void
408 MyApp :: refreshTorrents ()
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)
416 mySession->refreshActiveTorrents ();
418 else
420 myLastFullUpdateTime = now;
421 mySession->refreshAllTorrents ();
425 /***
426 ****
427 ***/
429 void
430 MyApp :: addTorrent (const QString& key)
432 const AddData addme (key);
434 if (addme.type != addme.NONE)
435 addTorrent (addme);
438 void
439 MyApp :: addTorrent (const AddData& addme)
441 if (!myPrefs->getBool (Prefs :: OPTIONS_PROMPT))
443 mySession->addTorrent (addme);
445 else
447 Options * o = new Options (*mySession, *myPrefs, addme, myWindow);
448 o->show ();
451 raise ();
454 /***
455 ****
456 ***/
458 void
459 MyApp :: raise ()
461 QApplication :: alert (myWindow);
464 bool
465 MyApp :: notify (const QString& title, const QString& body) const
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");
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 ();
488 /***
489 ****
490 ***/
493 main (int argc, char * argv[])
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);
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)
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)
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;
524 request.setArguments (arguments);
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 ();
533 if (delegated)
534 return 0;
536 tr_optind = 1;
537 MyApp app (argc, argv);
538 return app.exec ();