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 11437 2010-11-25 03:00:25Z charles $
17 #include <QDBusConnection>
18 #include <QDBusConnectionInterface>
20 #include <QDBusMessage>
21 #include <QDialogButtonBox>
24 #include <QLibraryInfo>
27 #include <libtransmission/transmission.h>
28 #include <libtransmission/tr-getopt.h>
29 #include <libtransmission/utils.h>
30 #include <libtransmission/version.h>
34 #include "dbus-adaptor.h"
35 #include "formatter.h"
40 #include "session-dialog.h"
41 #include "torrent-model.h"
47 const char * DBUS_SERVICE ( "com.transmissionbt.Transmission" );
48 const char * DBUS_OBJECT_PATH ( "/com/transmissionbt/Transmission" );
49 const char * DBUS_INTERFACE ( "com.transmissionbt.Transmission" );
51 const char * MY_CONFIG_NAME( "transmission" );
52 const char * MY_READABLE_NAME( "transmission-qt" );
55 const tr_option opts
[] =
57 { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
58 { 'm', "minimized", "Start minimized in system tray", "m", 0, NULL
},
59 { 'p', "port", "Port to use when connecting to an existing session", "p", 1, "<port>" },
60 { 'r', "remote", "Connect to an existing session at the specified hostname", "r", 1, "<host>" },
61 { 'u', "username", "Username to use when connecting to an existing session", "u", 1, "<username>" },
62 { 'v', "version", "Show version number and exit", "v", 0, NULL
},
63 { 'w', "password", "Password to use when connecting to an existing session", "w", 1, "<password>" },
64 { 0, NULL
, NULL
, NULL
, 0, NULL
}
71 " transmission [OPTIONS...] [torrent files]";
77 tr_getopt_usage( MY_READABLE_NAME
, getUsage( ), opts
);
83 STATS_REFRESH_INTERVAL_MSEC
= 3000,
84 SESSION_REFRESH_INTERVAL_MSEC
= 3000,
85 MODEL_REFRESH_INTERVAL_MSEC
= 3000
89 MyApp :: MyApp( int& argc
, char ** argv
):
90 QApplication( argc
, argv
),
91 myLastFullUpdateTime( 0 )
93 setApplicationName( MY_CONFIG_NAME
);
95 // install the qt translator
96 qtTranslator
.load( "qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath
));
97 installTranslator( &qtTranslator
);
99 // install the transmission translator
100 appTranslator
.load( QString(MY_CONFIG_NAME
) + "_" + QLocale::system().name(), QCoreApplication::applicationDirPath() + "/translations" );
101 installTranslator( &appTranslator
);
103 Formatter::initUnits( );
105 // set the default icon
107 icon
.addPixmap( QPixmap( ":/icons/transmission-16.png" ) );
108 icon
.addPixmap( QPixmap( ":/icons/transmission-22.png" ) );
109 icon
.addPixmap( QPixmap( ":/icons/transmission-24.png" ) );
110 icon
.addPixmap( QPixmap( ":/icons/transmission-32.png" ) );
111 icon
.addPixmap( QPixmap( ":/icons/transmission-48.png" ) );
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
) ) ) {
126 case 'g': configDir
= optarg
; break;
127 case 'p': port
= optarg
; break;
128 case 'r': host
= optarg
; break;
129 case 'u': username
= optarg
; break;
130 case 'w': password
= optarg
; break;
131 case 'm': minimized
= true; break;
132 case 'v': std::cerr
<< MY_READABLE_NAME
<< ' ' << LONG_VERSION_STRING
<< std::endl
; ::exit( 0 ); break;
133 case TR_OPT_ERR
: Utils::toStderr( QObject::tr( "Invalid option" ) ); showUsage( ); break;
134 default: filenames
.append( optarg
); break;
138 // set the fallback config dir
140 configDir
= tr_getDefaultConfigDir( MY_CONFIG_NAME
);
142 // is this the first time we've run transmission?
143 const bool firstTime
= !QFile(QDir(configDir
).absoluteFilePath("settings.json")).exists();
145 // initialize the prefs
146 myPrefs
= new Prefs ( configDir
);
148 myPrefs
->set( Prefs::SESSION_REMOTE_HOST
, host
);
150 myPrefs
->set( Prefs::SESSION_REMOTE_PORT
, port
);
152 myPrefs
->set( Prefs::SESSION_REMOTE_USERNAME
, username
);
154 myPrefs
->set( Prefs::SESSION_REMOTE_PASSWORD
, password
);
155 if( ( host
!= 0 ) || ( port
!= 0 ) || ( username
!= 0 ) || ( password
!= 0 ) )
156 myPrefs
->set( Prefs::SESSION_IS_REMOTE
, true );
158 mySession
= new Session( configDir
, *myPrefs
);
159 myModel
= new TorrentModel( *myPrefs
);
160 myWindow
= new TrMainWindow( *mySession
, *myPrefs
, *myModel
, minimized
);
161 myWatchDir
= new WatchDir( *myModel
);
163 // when the session gets torrent info, update the model
164 connect( mySession
, SIGNAL(torrentsUpdated(tr_benc
*,bool)), myModel
, SLOT(updateTorrents(tr_benc
*,bool)) );
165 connect( mySession
, SIGNAL(torrentsUpdated(tr_benc
*,bool)), myWindow
, SLOT(refreshActionSensitivity()) );
166 connect( mySession
, SIGNAL(torrentsRemoved(tr_benc
*)), myModel
, SLOT(removeTorrents(tr_benc
*)) );
167 // when the session source gets changed, request a full refresh
168 connect( mySession
, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
169 // when the model sees a torrent for the first time, ask the session for full info on it
170 connect( myModel
, SIGNAL(torrentsAdded(QSet
<int>)), mySession
, SLOT(initTorrents(QSet
<int>)) );
171 connect( myModel
, SIGNAL(torrentsAdded(QSet
<int>)), this, SLOT(onTorrentsAdded(QSet
<int>)) );
173 mySession
->initTorrents( );
174 mySession
->refreshSessionStats( );
176 // when torrents are added to the watch directory, tell the session
177 connect( myWatchDir
, SIGNAL(torrentFileAdded(QString
)), this, SLOT(addTorrent(QString
)) );
179 // init from preferences
181 initKeys
<< Prefs::DIR_WATCH
;
182 foreach( int key
, initKeys
)
184 connect( myPrefs
, SIGNAL(changed(int)), this, SLOT(refreshPref(const int)) );
186 QTimer
* timer
= &myModelTimer
;
187 connect( timer
, SIGNAL(timeout()), this, SLOT(refreshTorrents()) );
188 timer
->setSingleShot( false );
189 timer
->setInterval( MODEL_REFRESH_INTERVAL_MSEC
);
192 timer
= &myStatsTimer
;
193 connect( timer
, SIGNAL(timeout()), mySession
, SLOT(refreshSessionStats()) );
194 timer
->setSingleShot( false );
195 timer
->setInterval( STATS_REFRESH_INTERVAL_MSEC
);
198 timer
= &mySessionTimer
;
199 connect( timer
, SIGNAL(timeout()), mySession
, SLOT(refreshSessionInfo()) );
200 timer
->setSingleShot( false );
201 timer
->setInterval( SESSION_REFRESH_INTERVAL_MSEC
);
204 maybeUpdateBlocklist( );
207 mySession
->restart( );
209 QDialog
* d
= new SessionDialog( *mySession
, *myPrefs
, myWindow
);
213 if( !myPrefs
->getBool( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT
))
215 QDialog
* dialog
= new QDialog( myWindow
);
216 dialog
->setModal( true );
217 QVBoxLayout
* v
= new QVBoxLayout( dialog
);
218 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." ) );
219 l
->setWordWrap( true );
221 QDialogButtonBox
* box
= new QDialogButtonBox
;
222 box
->addButton( new QPushButton( tr( "&Cancel" ) ), QDialogButtonBox::RejectRole
);
223 QPushButton
* agree
= new QPushButton( tr( "I &Agree" ) );
224 agree
->setDefault( true );
225 box
->addButton( agree
, QDialogButtonBox::AcceptRole
);
226 box
->setSizePolicy( QSizePolicy::Expanding
, QSizePolicy::Fixed
);
227 box
->setOrientation( Qt::Horizontal
);
229 connect( box
, SIGNAL(rejected()), this, SLOT(quit()) );
230 connect( box
, SIGNAL(accepted()), dialog
, SLOT(deleteLater()) );
231 connect( box
, SIGNAL(accepted()), this, SLOT(consentGiven()) );
235 for( QStringList::const_iterator it
=filenames
.begin(), end
=filenames
.end(); it
!=end
; ++it
)
238 // register as the dbus handler for Transmission
239 new TrDBusAdaptor( this );
240 QDBusConnection bus
= QDBusConnection::sessionBus();
241 if( !bus
.registerService( DBUS_SERVICE
) )
242 std::cerr
<< "couldn't register " << DBUS_SERVICE
<< std::endl
;
243 if( !bus
.registerObject( DBUS_OBJECT_PATH
, this ) )
244 std::cerr
<< "couldn't register " << DBUS_OBJECT_PATH
<< std::endl
;
247 /* these two functions are for popping up desktop notification
248 * when new torrents are added */
250 MyApp :: onTorrentsAdded( QSet
<int> torrents
)
252 if( !myPrefs
->getBool( Prefs::SHOW_DESKTOP_NOTIFICATION
) )
255 foreach( int id
, torrents
)
257 Torrent
* tor
= myModel
->getTorrentFromId( id
);
258 if( !tor
->name().isEmpty( ) )
259 onNewTorrentChanged( id
);
260 else // wait until the torrent's INFO fields are loaded
261 connect( tor
, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)) );
265 MyApp :: onNewTorrentChanged( int id
)
267 Torrent
* tor
= myModel
->getTorrentFromId( id
);
269 if( tor
&& !tor
->name().isEmpty() )
271 const int age_secs
= tor
->dateAdded().secsTo(QDateTime::currentDateTime());
273 notify( tr( "Torrent Added" ), tor
->name( ) );
275 disconnect( tor
, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)) );
280 MyApp :: consentGiven( )
282 myPrefs
->set
<bool>( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT
, true );
287 const QRect
mainwinRect( myWindow
->geometry( ) );
293 myPrefs
->set( Prefs :: MAIN_WINDOW_HEIGHT
, std::max( 100, mainwinRect
.height( ) ) );
294 myPrefs
->set( Prefs :: MAIN_WINDOW_WIDTH
, std::max( 100, mainwinRect
.width( ) ) );
295 myPrefs
->set( Prefs :: MAIN_WINDOW_X
, mainwinRect
.x( ) );
296 myPrefs
->set( Prefs :: MAIN_WINDOW_Y
, mainwinRect
.y( ) );
305 MyApp :: refreshPref( int key
)
309 case Prefs :: BLOCKLIST_UPDATES_ENABLED
:
310 maybeUpdateBlocklist( );
313 case Prefs :: DIR_WATCH
:
314 case Prefs :: DIR_WATCH_ENABLED
: {
315 const QString
path( myPrefs
->getString( Prefs::DIR_WATCH
) );
316 const bool isEnabled( myPrefs
->getBool( Prefs::DIR_WATCH_ENABLED
) );
317 myWatchDir
->setPath( path
, isEnabled
);
327 MyApp :: maybeUpdateBlocklist( )
329 if( !myPrefs
->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED
) )
332 const QDateTime lastUpdatedAt
= myPrefs
->getDateTime( Prefs :: BLOCKLIST_DATE
);
333 const QDateTime nextUpdateAt
= lastUpdatedAt
.addDays( 7 );
334 const QDateTime now
= QDateTime::currentDateTime( );
335 if( now
< nextUpdateAt
)
337 mySession
->updateBlocklist( );
338 myPrefs
->set( Prefs :: BLOCKLIST_DATE
, now
);
343 MyApp :: onSessionSourceChanged( )
345 mySession
->initTorrents( );
346 mySession
->refreshSessionStats( );
347 mySession
->refreshSessionInfo( );
351 MyApp :: refreshTorrents( )
353 // usually we just poll the torrents that have shown recent activity,
354 // but we also periodically ask for updates on the others to ensure
355 // nothing's falling through the cracks.
356 const time_t now
= time( NULL
);
357 if( myLastFullUpdateTime
+ 60 >= now
)
358 mySession
->refreshActiveTorrents( );
360 myLastFullUpdateTime
= now
;
361 mySession
->refreshAllTorrents( );
370 MyApp :: addTorrent( const QString
& key
)
372 const AddData
addme( key
);
374 if( addme
.type
!= addme
.NONE
)
379 MyApp :: addTorrent( const AddData
& addme
)
381 if( !myPrefs
->getBool( Prefs :: OPTIONS_PROMPT
) )
383 mySession
->addTorrent( addme
);
385 else if( addme
.type
== addme
.URL
)
387 myWindow
->openURL( addme
.url
.toString( ) );
391 Options
* o
= new Options( *mySession
, *myPrefs
, addme
, myWindow
);
405 QApplication :: alert ( myWindow
);
409 MyApp :: notify( const QString
& title
, const QString
& body
) const
411 const QString dbusServiceName
= "org.freedesktop.Notifications";
412 const QString dbusInterfaceName
= "org.freedesktop.Notifications";
413 const QString dbusPath
= "/org/freedesktop/Notifications";
415 QDBusMessage m
= QDBusMessage::createMethodCall(dbusServiceName
, dbusPath
, dbusInterfaceName
, "Notify");
416 QList
<QVariant
> args
;
417 args
.append( "Transmission" ); // app_name
418 args
.append( 0U ); // replaces_id
419 args
.append( "transmission" ); // icon
420 args
.append( title
); // summary
421 args
.append( body
); // body
422 args
.append( QStringList( ) ); // actions - unused for plain passive popups
423 args
.append( QVariantMap( ) ); // hints - unused atm
424 args
.append( int32_t(-1) ); // use the default timeout period
425 m
.setArguments( args
);
426 QDBusMessage replyMsg
= QDBusConnection::sessionBus().call(m
);
427 //std::cerr << qPrintable(replyMsg.errorName()) << std::endl;
428 //std::cerr << qPrintable(replyMsg.errorMessage()) << std::endl;
429 return (replyMsg
.type() == QDBusMessage::ReplyMessage
) && !replyMsg
.arguments().isEmpty();
437 main( int argc
, char * argv
[] )
439 // find .torrents, URLs, magnet links, etc in the command-line args
443 char ** argvv
= argv
;
444 while( ( c
= tr_getopt( getUsage( ), argc
, (const char **)argvv
, opts
, &optarg
) ) )
445 if( c
== TR_OPT_UNK
)
446 addme
.append( optarg
);
448 // try to delegate the work to an existing copy of Transmission
449 // before starting ourselves...
450 bool delegated
= false;
451 QDBusConnection bus
= QDBusConnection::sessionBus();
452 for( int i
=0, n
=addme
.size(); i
<n
; ++i
)
454 QDBusMessage request
= QDBusMessage::createMethodCall( DBUS_SERVICE
,
458 QList
<QVariant
> arguments
;
459 AddData
a( addme
[i
] );
461 case AddData::URL
: arguments
.push_back( a
.url
.toString( ) ); break;
462 case AddData::MAGNET
: arguments
.push_back( a
.magnet
); break;
463 case AddData::FILENAME
: arguments
.push_back( a
.toBase64().constData() ); break;
464 case AddData::METAINFO
: arguments
.push_back( a
.toBase64().constData() ); break;
467 request
.setArguments( arguments
);
469 QDBusMessage response
= bus
.call( request
);
470 //std::cerr << qPrintable(response.errorName()) << std::endl;
471 //std::cerr << qPrintable(response.errorMessage()) << std::endl;
472 arguments
= response
.arguments( );
473 delegated
|= (arguments
.size()==1) && arguments
[0].toBool();
480 MyApp
app( argc
, argv
);