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 12031 2011-02-24 15:36:26Z 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 char * DBUS_SERVICE ( "com.transmissionbt.Transmission" );
49 const char * DBUS_OBJECT_PATH ( "/com/transmissionbt/Transmission" );
50 const char * DBUS_INTERFACE ( "com.transmissionbt.Transmission" );
52 const char * MY_CONFIG_NAME( "transmission" );
53 const char * MY_READABLE_NAME( "transmission-qt" );
56 const tr_option opts
[] =
58 { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
59 { 'm', "minimized", "Start minimized in system tray", "m", 0, NULL
},
60 { 'p', "port", "Port to use when connecting to an existing session", "p", 1, "<port>" },
61 { 'r', "remote", "Connect to an existing session at the specified hostname", "r", 1, "<host>" },
62 { 'u', "username", "Username to use when connecting to an existing session", "u", 1, "<username>" },
63 { 'v', "version", "Show version number and exit", "v", 0, NULL
},
64 { 'w', "password", "Password to use when connecting to an existing session", "w", 1, "<password>" },
65 { 0, NULL
, NULL
, NULL
, 0, NULL
}
72 " transmission [OPTIONS...] [torrent files]";
78 tr_getopt_usage( MY_READABLE_NAME
, getUsage( ), opts
);
84 STATS_REFRESH_INTERVAL_MSEC
= 3000,
85 SESSION_REFRESH_INTERVAL_MSEC
= 3000,
86 MODEL_REFRESH_INTERVAL_MSEC
= 3000
90 MyApp :: MyApp( int& argc
, char ** argv
):
91 QApplication( argc
, argv
),
92 myLastFullUpdateTime( 0 )
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
102 appTranslator
.load( QString(MY_CONFIG_NAME
) + "_" + QLocale::system().name(), QCoreApplication::applicationDirPath() + "/translations" );
104 appTranslator
.load( QString(MY_CONFIG_NAME
) + "_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath
) );
106 installTranslator( &appTranslator
);
108 Formatter::initUnits( );
110 // set the default icon
112 icon
.addPixmap( QPixmap( ":/icons/transmission-16.png" ) );
113 icon
.addPixmap( QPixmap( ":/icons/transmission-22.png" ) );
114 icon
.addPixmap( QPixmap( ":/icons/transmission-24.png" ) );
115 icon
.addPixmap( QPixmap( ":/icons/transmission-32.png" ) );
116 icon
.addPixmap( QPixmap( ":/icons/transmission-48.png" ) );
117 setWindowIcon( icon
);
119 // parse the command-line arguments
121 bool minimized
= false;
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
) ) ) {
131 case 'g': configDir
= optarg
; break;
132 case 'p': port
= optarg
; break;
133 case 'r': host
= optarg
; break;
134 case 'u': username
= optarg
; break;
135 case 'w': password
= optarg
; break;
136 case 'm': minimized
= true; break;
137 case 'v': std::cerr
<< MY_READABLE_NAME
<< ' ' << LONG_VERSION_STRING
<< std::endl
; ::exit( 0 ); break;
138 case TR_OPT_ERR
: Utils::toStderr( QObject::tr( "Invalid option" ) ); showUsage( ); break;
139 default: filenames
.append( optarg
); break;
143 // set the fallback config dir
145 configDir
= tr_getDefaultConfigDir( MY_CONFIG_NAME
);
147 // ensure our config directory exists
148 QDir
dir( configDir
);
150 dir
.mkpath( configDir
);
152 // is this the first time we've run transmission?
153 const bool firstTime
= !QFile(QDir(configDir
).absoluteFilePath("settings.json")).exists();
155 // initialize the prefs
156 myPrefs
= new Prefs ( configDir
);
158 myPrefs
->set( Prefs::SESSION_REMOTE_HOST
, host
);
160 myPrefs
->set( Prefs::SESSION_REMOTE_PORT
, port
);
162 myPrefs
->set( Prefs::SESSION_REMOTE_USERNAME
, username
);
164 myPrefs
->set( Prefs::SESSION_REMOTE_PASSWORD
, password
);
165 if( ( host
!= 0 ) || ( port
!= 0 ) || ( username
!= 0 ) || ( password
!= 0 ) )
166 myPrefs
->set( Prefs::SESSION_IS_REMOTE
, true );
168 mySession
= new Session( configDir
, *myPrefs
);
169 myModel
= new TorrentModel( *myPrefs
);
170 myWindow
= new TrMainWindow( *mySession
, *myPrefs
, *myModel
, minimized
);
171 myWatchDir
= new WatchDir( *myModel
);
173 // when the session gets torrent info, update the model
174 connect( mySession
, SIGNAL(torrentsUpdated(tr_benc
*,bool)), myModel
, SLOT(updateTorrents(tr_benc
*,bool)) );
175 connect( mySession
, SIGNAL(torrentsUpdated(tr_benc
*,bool)), myWindow
, SLOT(refreshActionSensitivity()) );
176 connect( mySession
, SIGNAL(torrentsRemoved(tr_benc
*)), myModel
, SLOT(removeTorrents(tr_benc
*)) );
177 // when the session source gets changed, request a full refresh
178 connect( mySession
, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
179 // when the model sees a torrent for the first time, ask the session for full info on it
180 connect( myModel
, SIGNAL(torrentsAdded(QSet
<int>)), mySession
, SLOT(initTorrents(QSet
<int>)) );
181 connect( myModel
, SIGNAL(torrentsAdded(QSet
<int>)), this, SLOT(onTorrentsAdded(QSet
<int>)) );
183 mySession
->initTorrents( );
184 mySession
->refreshSessionStats( );
186 // when torrents are added to the watch directory, tell the session
187 connect( myWatchDir
, SIGNAL(torrentFileAdded(QString
)), this, SLOT(addTorrent(QString
)) );
189 // init from preferences
191 initKeys
<< Prefs::DIR_WATCH
;
192 foreach( int key
, initKeys
)
194 connect( myPrefs
, SIGNAL(changed(int)), this, SLOT(refreshPref(const int)) );
196 QTimer
* timer
= &myModelTimer
;
197 connect( timer
, SIGNAL(timeout()), this, SLOT(refreshTorrents()) );
198 timer
->setSingleShot( false );
199 timer
->setInterval( MODEL_REFRESH_INTERVAL_MSEC
);
202 timer
= &myStatsTimer
;
203 connect( timer
, SIGNAL(timeout()), mySession
, SLOT(refreshSessionStats()) );
204 timer
->setSingleShot( false );
205 timer
->setInterval( STATS_REFRESH_INTERVAL_MSEC
);
208 timer
= &mySessionTimer
;
209 connect( timer
, SIGNAL(timeout()), mySession
, SLOT(refreshSessionInfo()) );
210 timer
->setSingleShot( false );
211 timer
->setInterval( SESSION_REFRESH_INTERVAL_MSEC
);
214 maybeUpdateBlocklist( );
217 mySession
->restart( );
219 QDialog
* d
= new SessionDialog( *mySession
, *myPrefs
, myWindow
);
223 if( !myPrefs
->getBool( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT
))
225 QDialog
* dialog
= new QDialog( myWindow
);
226 dialog
->setModal( true );
227 QVBoxLayout
* v
= new QVBoxLayout( dialog
);
228 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. You and you alone are fully responsible for exercising proper judgement and abiding by your local laws." ) );
229 l
->setWordWrap( true );
231 QDialogButtonBox
* box
= new QDialogButtonBox
;
232 box
->addButton( new QPushButton( tr( "&Cancel" ) ), QDialogButtonBox::RejectRole
);
233 QPushButton
* agree
= new QPushButton( tr( "I &Agree" ) );
234 agree
->setDefault( true );
235 box
->addButton( agree
, QDialogButtonBox::AcceptRole
);
236 box
->setSizePolicy( QSizePolicy::Expanding
, QSizePolicy::Fixed
);
237 box
->setOrientation( Qt::Horizontal
);
239 connect( box
, SIGNAL(rejected()), this, SLOT(quit()) );
240 connect( box
, SIGNAL(accepted()), dialog
, SLOT(deleteLater()) );
241 connect( box
, SIGNAL(accepted()), this, SLOT(consentGiven()) );
245 for( QStringList::const_iterator it
=filenames
.begin(), end
=filenames
.end(); it
!=end
; ++it
)
248 // register as the dbus handler for Transmission
249 new TrDBusAdaptor( this );
250 QDBusConnection bus
= QDBusConnection::sessionBus();
251 if( !bus
.registerService( DBUS_SERVICE
) )
252 std::cerr
<< "couldn't register " << DBUS_SERVICE
<< std::endl
;
253 if( !bus
.registerObject( DBUS_OBJECT_PATH
, this ) )
254 std::cerr
<< "couldn't register " << DBUS_OBJECT_PATH
<< std::endl
;
257 /* these two functions are for popping up desktop notification
258 * when new torrents are added */
260 MyApp :: onTorrentsAdded( QSet
<int> torrents
)
262 if( !myPrefs
->getBool( Prefs::SHOW_DESKTOP_NOTIFICATION
) )
265 foreach( int id
, torrents
)
267 Torrent
* tor
= myModel
->getTorrentFromId( id
);
268 if( !tor
->name().isEmpty( ) )
269 onNewTorrentChanged( id
);
270 else // wait until the torrent's INFO fields are loaded
271 connect( tor
, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)) );
275 MyApp :: onNewTorrentChanged( int id
)
277 Torrent
* tor
= myModel
->getTorrentFromId( id
);
279 if( tor
&& !tor
->name().isEmpty() )
281 const int age_secs
= tor
->dateAdded().secsTo(QDateTime::currentDateTime());
283 notify( tr( "Torrent Added" ), tor
->name( ) );
285 disconnect( tor
, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)) );
290 MyApp :: consentGiven( )
292 myPrefs
->set
<bool>( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT
, true );
297 const QRect
mainwinRect( myWindow
->geometry( ) );
303 myPrefs
->set( Prefs :: MAIN_WINDOW_HEIGHT
, std::max( 100, mainwinRect
.height( ) ) );
304 myPrefs
->set( Prefs :: MAIN_WINDOW_WIDTH
, std::max( 100, mainwinRect
.width( ) ) );
305 myPrefs
->set( Prefs :: MAIN_WINDOW_X
, mainwinRect
.x( ) );
306 myPrefs
->set( Prefs :: MAIN_WINDOW_Y
, mainwinRect
.y( ) );
315 MyApp :: refreshPref( int key
)
319 case Prefs :: BLOCKLIST_UPDATES_ENABLED
:
320 maybeUpdateBlocklist( );
323 case Prefs :: DIR_WATCH
:
324 case Prefs :: DIR_WATCH_ENABLED
: {
325 const QString
path( myPrefs
->getString( Prefs::DIR_WATCH
) );
326 const bool isEnabled( myPrefs
->getBool( Prefs::DIR_WATCH_ENABLED
) );
327 myWatchDir
->setPath( path
, isEnabled
);
337 MyApp :: maybeUpdateBlocklist( )
339 if( !myPrefs
->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED
) )
342 const QDateTime lastUpdatedAt
= myPrefs
->getDateTime( Prefs :: BLOCKLIST_DATE
);
343 const QDateTime nextUpdateAt
= lastUpdatedAt
.addDays( 7 );
344 const QDateTime now
= QDateTime::currentDateTime( );
345 if( now
< nextUpdateAt
)
347 mySession
->updateBlocklist( );
348 myPrefs
->set( Prefs :: BLOCKLIST_DATE
, now
);
353 MyApp :: onSessionSourceChanged( )
355 mySession
->initTorrents( );
356 mySession
->refreshSessionStats( );
357 mySession
->refreshSessionInfo( );
361 MyApp :: refreshTorrents( )
363 // usually we just poll the torrents that have shown recent activity,
364 // but we also periodically ask for updates on the others to ensure
365 // nothing's falling through the cracks.
366 const time_t now
= time( NULL
);
367 if( myLastFullUpdateTime
+ 60 >= now
)
368 mySession
->refreshActiveTorrents( );
370 myLastFullUpdateTime
= now
;
371 mySession
->refreshAllTorrents( );
380 MyApp :: addTorrent( const QString
& key
)
382 const AddData
addme( key
);
384 if( addme
.type
!= addme
.NONE
)
389 MyApp :: addTorrent( const AddData
& addme
)
391 if( !myPrefs
->getBool( Prefs :: OPTIONS_PROMPT
) )
393 mySession
->addTorrent( addme
);
395 else if( addme
.type
== addme
.URL
)
397 myWindow
->openURL( addme
.url
.toString( ) );
401 Options
* o
= new Options( *mySession
, *myPrefs
, addme
, myWindow
);
415 QApplication :: alert ( myWindow
);
419 MyApp :: notify( const QString
& title
, const QString
& body
) const
421 const QString dbusServiceName
= "org.freedesktop.Notifications";
422 const QString dbusInterfaceName
= "org.freedesktop.Notifications";
423 const QString dbusPath
= "/org/freedesktop/Notifications";
425 QDBusMessage m
= QDBusMessage::createMethodCall(dbusServiceName
, dbusPath
, dbusInterfaceName
, "Notify");
426 QList
<QVariant
> args
;
427 args
.append( "Transmission" ); // app_name
428 args
.append( 0U ); // replaces_id
429 args
.append( "transmission" ); // icon
430 args
.append( title
); // summary
431 args
.append( body
); // body
432 args
.append( QStringList( ) ); // actions - unused for plain passive popups
433 args
.append( QVariantMap( ) ); // hints - unused atm
434 args
.append( int32_t(-1) ); // use the default timeout period
435 m
.setArguments( args
);
436 QDBusMessage replyMsg
= QDBusConnection::sessionBus().call(m
);
437 //std::cerr << qPrintable(replyMsg.errorName()) << std::endl;
438 //std::cerr << qPrintable(replyMsg.errorMessage()) << std::endl;
439 return (replyMsg
.type() == QDBusMessage::ReplyMessage
) && !replyMsg
.arguments().isEmpty();
447 main( int argc
, char * argv
[] )
449 // find .torrents, URLs, magnet links, etc in the command-line args
453 char ** argvv
= argv
;
454 while( ( c
= tr_getopt( getUsage( ), argc
, (const char **)argvv
, opts
, &optarg
) ) )
455 if( c
== TR_OPT_UNK
)
456 addme
.append( optarg
);
458 // try to delegate the work to an existing copy of Transmission
459 // before starting ourselves...
460 bool delegated
= false;
461 QDBusConnection bus
= QDBusConnection::sessionBus();
462 for( int i
=0, n
=addme
.size(); i
<n
; ++i
)
464 QDBusMessage request
= QDBusMessage::createMethodCall( DBUS_SERVICE
,
468 QList
<QVariant
> arguments
;
469 AddData
a( addme
[i
] );
471 case AddData::URL
: arguments
.push_back( a
.url
.toString( ) ); break;
472 case AddData::MAGNET
: arguments
.push_back( a
.magnet
); break;
473 case AddData::FILENAME
: arguments
.push_back( a
.toBase64().constData() ); break;
474 case AddData::METAINFO
: arguments
.push_back( a
.toBase64().constData() ); break;
477 request
.setArguments( arguments
);
479 QDBusMessage response
= bus
.call( request
);
480 //std::cerr << qPrintable(response.errorName()) << std::endl;
481 //std::cerr << qPrintable(response.errorMessage()) << std::endl;
482 arguments
= response
.arguments( );
483 delegated
|= (arguments
.size()==1) && arguments
[0].toBool();
490 MyApp
app( argc
, argv
);