transmission: update from 2.13 to 2.22
[tomato.git] / release / src / router / transmission / qt / app.cc
blobdf922309e75484453af8157f1f252bbf053135c6
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 12031 2011-02-24 15:36:26Z 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 <QRect>
26 #include <QtGlobal>
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 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 }
68 const char*
69 getUsage( void )
71 return "Usage:\n"
72 " transmission [OPTIONS...] [torrent files]";
75 void
76 showUsage( void )
78 tr_getopt_usage( MY_READABLE_NAME, getUsage( ), opts );
79 exit( 0 );
82 enum
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
101 #ifdef Q_OS_WIN32
102 appTranslator.load( QString(MY_CONFIG_NAME) + "_" + QLocale::system().name(), QCoreApplication::applicationDirPath() + "/translations" );
103 #else
104 appTranslator.load( QString(MY_CONFIG_NAME) + "_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath) );
105 #endif
106 installTranslator( &appTranslator );
108 Formatter::initUnits( );
110 // set the default icon
111 QIcon 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
120 int c;
121 bool minimized = false;
122 const char * optarg;
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 ) ) ) {
130 switch( c ) {
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
144 if( configDir == 0 )
145 configDir = tr_getDefaultConfigDir( MY_CONFIG_NAME );
147 // ensure our config directory exists
148 QDir dir( configDir );
149 if( !dir.exists() )
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 );
157 if( host != 0 )
158 myPrefs->set( Prefs::SESSION_REMOTE_HOST, host );
159 if( port != 0 )
160 myPrefs->set( Prefs::SESSION_REMOTE_PORT, port );
161 if( username != 0 )
162 myPrefs->set( Prefs::SESSION_REMOTE_USERNAME, username );
163 if( password != 0 )
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
190 QList<int> initKeys;
191 initKeys << Prefs::DIR_WATCH;
192 foreach( int key, initKeys )
193 refreshPref( key );
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 );
200 timer->start( );
202 timer = &myStatsTimer;
203 connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionStats()) );
204 timer->setSingleShot( false );
205 timer->setInterval( STATS_REFRESH_INTERVAL_MSEC );
206 timer->start( );
208 timer = &mySessionTimer;
209 connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionInfo()) );
210 timer->setSingleShot( false );
211 timer->setInterval( SESSION_REFRESH_INTERVAL_MSEC );
212 timer->start( );
214 maybeUpdateBlocklist( );
216 if( !firstTime )
217 mySession->restart( );
218 else {
219 QDialog * d = new SessionDialog( *mySession, *myPrefs, myWindow );
220 d->show( );
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 );
230 v->addWidget( l );
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 );
238 v->addWidget( box );
239 connect( box, SIGNAL(rejected()), this, SLOT(quit()) );
240 connect( box, SIGNAL(accepted()), dialog, SLOT(deleteLater()) );
241 connect( box, SIGNAL(accepted()), this, SLOT(consentGiven()) );
242 dialog->show();
245 for( QStringList::const_iterator it=filenames.begin(), end=filenames.end(); it!=end; ++it )
246 addTorrent( *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 */
259 void
260 MyApp :: onTorrentsAdded( QSet<int> torrents )
262 if( !myPrefs->getBool( Prefs::SHOW_DESKTOP_NOTIFICATION ) )
263 return;
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)) );
274 void
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());
282 if( age_secs < 30 )
283 notify( tr( "Torrent Added" ), tor->name( ) );
285 disconnect( tor, SIGNAL(torrentChanged(int)), this, SLOT(onNewTorrentChanged(int)) );
289 void
290 MyApp :: consentGiven( )
292 myPrefs->set<bool>( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true );
295 MyApp :: ~MyApp( )
297 const QRect mainwinRect( myWindow->geometry( ) );
298 delete myWatchDir;
299 delete myWindow;
300 delete myModel;
301 delete mySession;
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( ) );
307 delete myPrefs;
310 /***
311 ****
312 ***/
314 void
315 MyApp :: refreshPref( int key )
317 switch( key )
319 case Prefs :: BLOCKLIST_UPDATES_ENABLED:
320 maybeUpdateBlocklist( );
321 break;
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 );
328 break;
331 default:
332 break;
336 void
337 MyApp :: maybeUpdateBlocklist( )
339 if( !myPrefs->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED ) )
340 return;
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 );
352 void
353 MyApp :: onSessionSourceChanged( )
355 mySession->initTorrents( );
356 mySession->refreshSessionStats( );
357 mySession->refreshSessionInfo( );
360 void
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( );
369 else {
370 myLastFullUpdateTime = now;
371 mySession->refreshAllTorrents( );
375 /***
376 ****
377 ***/
379 void
380 MyApp :: addTorrent( const QString& key )
382 const AddData addme( key );
384 if( addme.type != addme.NONE )
385 addTorrent( addme );
388 void
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( ) );
399 else
401 Options * o = new Options( *mySession, *myPrefs, addme, myWindow );
402 o->show( );
405 raise( );
408 /***
409 ****
410 ***/
412 void
413 MyApp :: raise( )
415 QApplication :: alert ( myWindow );
418 bool
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();
442 /***
443 ****
444 ***/
447 main( int argc, char * argv[] )
449 // find .torrents, URLs, magnet links, etc in the command-line args
450 int c;
451 QStringList addme;
452 const char * optarg;
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,
465 DBUS_OBJECT_PATH,
466 DBUS_INTERFACE,
467 "AddMetainfo" );
468 QList<QVariant> arguments;
469 AddData a( addme[i] );
470 switch( a.type ) {
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;
475 default: 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();
486 if( delegated )
487 return 0;
489 tr_optind = 1;
490 MyApp app( argc, argv );
491 return app.exec( );