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 $
17 #include <QDBusConnection>
18 #include <QDBusConnectionInterface>
20 #include <QDBusMessage>
21 #include <QDialogButtonBox>
24 #include <QLibraryInfo>
28 #include <libtransmission/transmission.h>
29 #include <libtransmission/tr-getopt.h>
30 #include <libtransmission/utils.h>
31 #include <libtransmission/version.h>
35 #include "dbus-adaptor.h"
36 #include "formatter.h"
41 #include "session-dialog.h"
42 #include "torrent-model.h"
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
}
70 " transmission [OPTIONS...] [torrent files]";
76 tr_getopt_usage (MY_READABLE_NAME
, getUsage (), opts
);
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
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
116 bool minimized
= false;
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
)))
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
142 configDir
= tr_getDefaultConfigDir ("transmission");
144 // ensure our config directory exists
145 QDir
dir (configDir
);
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
);
155 myPrefs
->set (Prefs::SESSION_REMOTE_HOST
, host
);
157 myPrefs
->set (Prefs::SESSION_REMOTE_PORT
, port
);
159 myPrefs
->set (Prefs::SESSION_REMOTE_USERNAME
, username
);
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
))
167 // start as minimized only if the system tray present
168 if (!myPrefs
->getBool (Prefs::SHOW_TRAY_ICON
))
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
194 initKeys
<< Prefs::DIR_WATCH
;
195 foreach (int key
, initKeys
)
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
);
205 timer
= &myStatsTimer
;
206 connect (timer
, SIGNAL (timeout ()), mySession
, SLOT (refreshSessionStats ()));
207 timer
->setSingleShot (false);
208 timer
->setInterval (STATS_REFRESH_INTERVAL_MSEC
);
211 timer
= &mySessionTimer
;
212 connect (timer
, SIGNAL (timeout ()), mySession
, SLOT (refreshSessionInfo ()));
213 timer
->setSingleShot (false);
214 timer
->setInterval (SESSION_REFRESH_INTERVAL_MSEC
);
217 maybeUpdateBlocklist ();
221 mySession
->restart ();
225 QDialog
* d
= new SessionDialog (*mySession
, *myPrefs
, myWindow
);
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);
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
);
245 connect (box
, SIGNAL (rejected ()), this, SLOT (quit ()));
246 connect (box
, SIGNAL (accepted ()), dialog
, SLOT (deleteLater ()));
247 connect (box
, SIGNAL (accepted ()), this, SLOT (consentGiven ()));
251 for (QStringList::const_iterator it
=filenames
.begin (), end
=filenames
.end (); it
!=end
; ++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 */
266 MyApp :: onTorrentsAdded (QSet
<int> torrents
)
268 if (!myPrefs
->getBool (Prefs::SHOW_NOTIFICATION_ON_ADD
))
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)));
281 onNewTorrentChanged (id
);
284 connect (tor
, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
290 MyApp :: onTorrentCompleted (int id
)
292 Torrent
* tor
= myModel
->getTorrentFromId (id
);
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 ();
304 QProcess::execute (myPrefs
->getString (Prefs::COMPLETE_SOUND_COMMAND
));
308 disconnect (tor
, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
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 ());
321 notify (tr ("Torrent Added"), tor
->name ());
323 disconnect (tor
, SIGNAL (torrentChanged (int)), this, SLOT (onNewTorrentChanged (int)));
326 connect (tor
, SIGNAL (torrentCompleted (int)), this, SLOT (onTorrentCompleted (int)));
335 MyApp :: consentGiven ()
337 myPrefs
->set
<bool> (Prefs::USER_HAS_GIVEN_INFORMED_CONSENT
, true);
342 const QRect
mainwinRect (myWindow
->geometry ());
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 ());
360 MyApp :: refreshPref (int key
)
364 case Prefs :: BLOCKLIST_UPDATES_ENABLED
:
365 maybeUpdateBlocklist ();
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
);
383 MyApp :: maybeUpdateBlocklist ()
385 if (!myPrefs
->getBool (Prefs :: BLOCKLIST_UPDATES_ENABLED
))
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
);
400 MyApp :: onSessionSourceChanged ()
402 mySession
->initTorrents ();
403 mySession
->refreshSessionStats ();
404 mySession
->refreshSessionInfo ();
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 ();
420 myLastFullUpdateTime
= now
;
421 mySession
->refreshAllTorrents ();
430 MyApp :: addTorrent (const QString
& key
)
432 const AddData
addme (key
);
434 if (addme
.type
!= addme
.NONE
)
439 MyApp :: addTorrent (const AddData
& addme
)
441 if (!myPrefs
->getBool (Prefs :: OPTIONS_PROMPT
))
443 mySession
->addTorrent (addme
);
447 Options
* o
= new Options (*mySession
, *myPrefs
, addme
, myWindow
);
461 QApplication :: alert (myWindow
);
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 ();
493 main (int argc
, char * argv
[])
495 // find .torrents, URLs, magnet links, etc in the command-line args
499 char ** argvv
= argv
;
500 while ( (c
= tr_getopt (getUsage (), argc
, (const char **)argvv
, opts
, &optarg
)))
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
,
513 QString::fromUtf8 ("AddMetainfo"));
514 QList
<QVariant
> arguments
;
515 AddData
a (addme
[i
]);
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;
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 ();
537 MyApp
app (argc
, argv
);