source: trunk/qt/app.cc @ 10775

Last change on this file since 10775 was 10775, checked in by Longinus00, 12 years ago

(qt) #3269: When changing session source we should request a full refresh right away
r10770 is actually for #3282: After removing a torrent list jumps to the very top

  • Property svn:keywords set to Date Rev Author Id
File size: 13.5 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: app.cc 10775 2010-06-16 03:11:10Z Longinus00 $
11 */
12
13#include <cassert>
14#include <ctime>
15#include <iostream>
16
17#include <QDBusConnection>
18#include <QDBusError>
19#include <QDBusMessage>
20#include <QDialogButtonBox>
21#include <QIcon>
22#include <QLabel>
23#include <QLibraryInfo>
24#include <QRect>
25#include <QTranslator>
26
27#include <libtransmission/transmission.h>
28#include <libtransmission/tr-getopt.h>
29#include <libtransmission/version.h>
30
31#include "app.h"
32#include "dbus-adaptor.h"
33#include "mainwin.h"
34#include "options.h"
35#include "prefs.h"
36#include "session.h"
37#include "session-dialog.h"
38#include "torrent-model.h"
39#include "utils.h"
40#include "watchdir.h"
41
42namespace
43{
44    const char * DBUS_SERVICE     ( "com.transmissionbt.Transmission"  );
45    const char * DBUS_OBJECT_PATH ( "/com/transmissionbt/Transmission" );
46    const char * DBUS_INTERFACE   ( "com.transmissionbt.Transmission"  );
47
48    const char * MY_NAME( "transmission" );
49
50    const tr_option opts[] =
51    {
52        { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
53        { 'm', "minimized",  "Start minimized in system tray", "m", 0, NULL },
54        { 'p', "port",  "Port to use when connecting to an existing session", "p", 1, "<port>" },
55        { 'r', "remote",  "Connect to an existing session at the specified hostname", "r", 1, "<host>" },
56        { 'u', "username", "Username to use when connecting to an existing session", "u", 1, "<username>" },
57        { 'v', "version", "Show version number and exit", "v", 0, NULL },
58        { 'w', "password", "Password to use when connecting to an existing session", "w", 1, "<password>" },
59        { 0, NULL, NULL, NULL, 0, NULL }
60    };
61
62    const char*
63    getUsage( void )
64    {
65        return "Usage:\n"
66               "  transmission [OPTIONS...] [torrent files]";
67    }
68
69    void
70    showUsage( void )
71    {
72        tr_getopt_usage( MY_NAME, getUsage( ), opts );
73        exit( 0 );
74    }
75
76    enum
77    {
78        STATS_REFRESH_INTERVAL_MSEC = 3000,
79        SESSION_REFRESH_INTERVAL_MSEC = 3000,
80        MODEL_REFRESH_INTERVAL_MSEC = 3000
81    };
82}
83
84MyApp :: MyApp( int& argc, char ** argv ):
85    QApplication( argc, argv ),
86    myLastFullUpdateTime( 0 )
87{
88    setApplicationName( MY_NAME );
89
90    // install the qt translator
91    QTranslator * t = new QTranslator( );
92    t->load( "qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
93    installTranslator( t );
94
95    // install the transmission translator
96    t = new QTranslator( );
97    t->load( QString(MY_NAME) + "_" + QLocale::system().name() );
98    installTranslator( t );
99
100    // set the default icon
101    QIcon icon;
102    icon.addPixmap( QPixmap( ":/icons/transmission-16.png" ) );
103    icon.addPixmap( QPixmap( ":/icons/transmission-22.png" ) );
104    icon.addPixmap( QPixmap( ":/icons/transmission-24.png" ) );
105    icon.addPixmap( QPixmap( ":/icons/transmission-32.png" ) );
106    icon.addPixmap( QPixmap( ":/icons/transmission-48.png" ) );
107    setWindowIcon( icon );
108
109    // parse the command-line arguments
110    int c;
111    bool minimized = false;
112    const char * optarg;
113    const char * host = 0;
114    const char * port = 0;
115    const char * username = 0;
116    const char * password = 0;
117    const char * configDir = 0;
118    QStringList filenames;
119    while( ( c = tr_getopt( getUsage( ), argc, (const char**)argv, opts, &optarg ) ) ) {
120        switch( c ) {
121            case 'g': configDir = optarg; break;
122            case 'p': port = optarg; break;
123            case 'r': host = optarg; break;
124            case 'u': username = optarg; break;
125            case 'w': password = optarg; break;
126            case 'm': minimized = true; break;
127            case 'v':        Utils::toStderr( QObject::tr( "transmission %1" ).arg( LONG_VERSION_STRING ) ); ::exit( 0 ); break;
128            case TR_OPT_ERR: Utils::toStderr( QObject::tr( "Invalid option" ) ); showUsage( ); break;
129            default:         filenames.append( optarg ); break;
130        }
131    }
132
133    // set the fallback config dir
134    if( configDir == 0 )
135        configDir = tr_getDefaultConfigDir( MY_NAME );
136
137    // is this the first time we've run transmission?
138    const bool firstTime = !QFile(QDir(configDir).absoluteFilePath("settings.json")).exists();
139
140    // initialize the prefs
141    myPrefs = new Prefs ( configDir );
142    if( host != 0 )
143        myPrefs->set( Prefs::SESSION_REMOTE_HOST, host );
144    if( port != 0 )
145        myPrefs->set( Prefs::SESSION_REMOTE_PORT, port );
146    if( username != 0 )
147        myPrefs->set( Prefs::SESSION_REMOTE_USERNAME, username );
148    if( password != 0 )
149        myPrefs->set( Prefs::SESSION_REMOTE_PASSWORD, password );
150    if( ( host != 0 ) || ( port != 0 ) || ( username != 0 ) || ( password != 0 ) )
151        myPrefs->set( Prefs::SESSION_IS_REMOTE, true );
152
153    mySession = new Session( configDir, *myPrefs );
154    myModel = new TorrentModel( *myPrefs );
155    myWindow = new TrMainWindow( *mySession, *myPrefs, *myModel, minimized );
156    myWatchDir = new WatchDir( *myModel );
157
158    // when the session gets torrent info, update the model
159    connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myModel, SLOT(updateTorrents(tr_benc*,bool)) );
160    connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myWindow, SLOT(refreshActionSensitivity()) );
161    connect( mySession, SIGNAL(torrentsRemoved(tr_benc*)), myModel, SLOT(removeTorrents(tr_benc*)) );
162    // when the session source gets changed, request a full refresh
163    connect( mySession, SIGNAL(sourceChanged()), this, SLOT(onSessionSourceChanged()) );
164    // when the model sees a torrent for the first time, ask the session for full info on it
165    connect( myModel, SIGNAL(torrentsAdded(QSet<int>)), mySession, SLOT(initTorrents(QSet<int>)) );
166
167    mySession->initTorrents( );
168    mySession->refreshSessionStats( );
169
170    // when torrents are added to the watch directory, tell the session
171    connect( myWatchDir, SIGNAL(torrentFileAdded(QString)), this, SLOT(addTorrent(QString)) );
172
173    // init from preferences
174    QList<int> initKeys;
175    initKeys << Prefs::DIR_WATCH;
176    foreach( int key, initKeys )
177        refreshPref( key );
178    connect( myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(const int)) );
179
180    QTimer * timer = &myModelTimer;
181    connect( timer, SIGNAL(timeout()), this, SLOT(refreshTorrents()) );
182    timer->setSingleShot( false );
183    timer->setInterval( MODEL_REFRESH_INTERVAL_MSEC );
184    timer->start( );
185
186    timer = &myStatsTimer;
187    connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionStats()) );
188    timer->setSingleShot( false );
189    timer->setInterval( STATS_REFRESH_INTERVAL_MSEC );
190    timer->start( );
191
192    timer = &mySessionTimer;
193    connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionInfo()) );
194    timer->setSingleShot( false );
195    timer->setInterval( SESSION_REFRESH_INTERVAL_MSEC );
196    timer->start( );
197
198    maybeUpdateBlocklist( );
199
200    if( !firstTime )
201        mySession->restart( );
202    else {
203        QDialog * d = new SessionDialog( *mySession, *myPrefs, myWindow );
204        d->show( );
205    }
206
207    if( !myPrefs->getBool( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT ))
208    {
209        QDialog * dialog = new QDialog( myWindow );
210        dialog->setModal( true );
211        QVBoxLayout * v = new QVBoxLayout( dialog );
212        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." ) );
213        l->setWordWrap( true );
214        v->addWidget( l );
215        QDialogButtonBox * box = new QDialogButtonBox;
216        box->addButton( new QPushButton( tr( "&Cancel" ) ), QDialogButtonBox::RejectRole );
217        QPushButton * agree = new QPushButton( tr( "I &Agree" ) );
218        agree->setDefault( true );
219        box->addButton( agree, QDialogButtonBox::AcceptRole );
220        box->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
221        box->setOrientation( Qt::Horizontal );
222        v->addWidget( box );
223        connect( box, SIGNAL(rejected()), this, SLOT(quit()) );
224        connect( box, SIGNAL(accepted()), dialog, SLOT(deleteLater()) );
225        connect( box, SIGNAL(accepted()), this, SLOT(consentGiven()) );
226        dialog->show();
227    }
228
229    for( QStringList::const_iterator it=filenames.begin(), end=filenames.end(); it!=end; ++it )
230        addTorrent( *it );
231
232    // register as the dbus handler for Transmission
233    new TrDBusAdaptor( this );
234    QDBusConnection bus = QDBusConnection::sessionBus();
235    if (!bus.registerService("com.transmissionbt.Transmission"))
236        fprintf(stderr, "%s\n", qPrintable(bus.lastError().message()));
237    if( !bus.registerObject( "/com/transmissionbt/Transmission", this ))
238        fprintf(stderr, "%s\n", qPrintable(bus.lastError().message()));
239}
240
241void
242MyApp :: consentGiven( )
243{
244    myPrefs->set<bool>( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true );
245}
246
247MyApp :: ~MyApp( )
248{
249    const QRect mainwinRect( myWindow->geometry( ) );
250    delete myWatchDir;
251    delete myWindow;
252    delete myModel;
253    delete mySession;
254
255    myPrefs->set( Prefs :: MAIN_WINDOW_HEIGHT, std::max( 100, mainwinRect.height( ) ) );
256    myPrefs->set( Prefs :: MAIN_WINDOW_WIDTH, std::max( 100, mainwinRect.width( ) ) );
257    myPrefs->set( Prefs :: MAIN_WINDOW_X, mainwinRect.x( ) );
258    myPrefs->set( Prefs :: MAIN_WINDOW_Y, mainwinRect.y( ) );
259    delete myPrefs;
260}
261
262/***
263****
264***/
265
266void
267MyApp :: refreshPref( int key )
268{
269    switch( key )
270    {
271        case Prefs :: BLOCKLIST_UPDATES_ENABLED:
272            maybeUpdateBlocklist( );
273            break;
274
275        case Prefs :: DIR_WATCH:
276        case Prefs :: DIR_WATCH_ENABLED: {
277            const QString path( myPrefs->getString( Prefs::DIR_WATCH ) );
278            const bool isEnabled( myPrefs->getBool( Prefs::DIR_WATCH_ENABLED ) );
279            myWatchDir->setPath( path, isEnabled );
280            break;
281        }
282
283        default:
284            break;
285    }
286}
287
288void
289MyApp :: maybeUpdateBlocklist( )
290{
291    if( !myPrefs->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED ) )
292        return;
293
294     const QDateTime lastUpdatedAt = myPrefs->getDateTime( Prefs :: BLOCKLIST_DATE );
295     const QDateTime nextUpdateAt = lastUpdatedAt.addDays( 7 );
296     const QDateTime now = QDateTime::currentDateTime( );
297     if( now < nextUpdateAt )
298     {
299         mySession->updateBlocklist( );
300         myPrefs->set( Prefs :: BLOCKLIST_DATE, now );
301     }
302}
303
304void
305MyApp :: onSessionSourceChanged( )
306{
307    mySession->initTorrents( );
308    mySession->refreshSessionStats( );
309    mySession->refreshSessionInfo( );
310}
311
312void
313MyApp :: refreshTorrents( )
314{
315    // usually we just poll the torrents that have shown recent activity,
316    // but we also periodically ask for updates on the others to ensure
317    // nothing's falling through the cracks.
318    const time_t now = time( NULL );
319    if( myLastFullUpdateTime + 60 >= now )
320        mySession->refreshActiveTorrents( );
321    else {
322        myLastFullUpdateTime = now;
323        mySession->refreshAllTorrents( );
324    }
325}
326
327/***
328****
329***/
330
331void
332MyApp :: addTorrent( const QString& key )
333{
334    if( !myPrefs->getBool( Prefs :: OPTIONS_PROMPT ) )
335    {
336        mySession->addTorrent( key );
337    }
338    else if( Utils::isMagnetLink( key ) || QFile( key ).exists( ) )
339    {
340        Options * o = new Options( *mySession, *myPrefs, key, myWindow );
341        o->show( );
342    }
343    else if( Utils::isURL( key ) )
344    {
345        myWindow->openURL( key );
346    }
347
348    raise( );
349}
350
351void
352MyApp :: raise( )
353{
354    QApplication :: alert ( myWindow );
355}
356
357/***
358****
359***/
360
361int
362main( int argc, char * argv[] )
363{
364    // find .torrents, URLs, magnet links, etc in the command-line args
365    int c;
366    QStringList addme;
367    const char * optarg;
368    char ** argvv = argv;
369    while( ( c = tr_getopt( getUsage( ), argc, (const char **)argvv, opts, &optarg ) ) )
370        if( c == TR_OPT_UNK )
371            addme.append( optarg );
372
373    // try to delegate the work to an existing copy of Transmission
374    // before starting ourselves...
375    bool delegated = false;
376    QDBusConnection bus = QDBusConnection::sessionBus();
377    for( int i=0, n=addme.size(); i<n; ++i )
378    {
379        const QString key = addme[i];
380
381        QDBusMessage request = QDBusMessage::createMethodCall( DBUS_SERVICE,
382                                                               DBUS_OBJECT_PATH,
383                                                               DBUS_INTERFACE,
384                                                               "AddMetainfo" );
385        QList<QVariant> arguments;
386        arguments.push_back( QVariant( key ) );
387        request.setArguments( arguments );
388
389        QDBusMessage response = bus.call( request );
390        arguments = response.arguments( );
391        delegated |= (arguments.size()==1) && arguments[0].toBool();
392    }
393    if( addme.empty() )
394    {
395        QDBusMessage request = QDBusMessage::createMethodCall( DBUS_SERVICE,
396                                                               DBUS_OBJECT_PATH,
397                                                               DBUS_INTERFACE,
398                                                               "PresentWindow" );
399        QDBusMessage response = bus.call( request );
400        QList<QVariant> arguments = response.arguments( );
401        delegated |= (arguments.size()==1) && arguments[0].toBool();
402    }
403
404    if( delegated )
405        return 0;
406
407    tr_optind = 1;
408    MyApp app( argc, argv );
409    return app.exec( );
410}
Note: See TracBrowser for help on using the repository browser.