source: trunk/qt/app.cc @ 10955

Last change on this file since 10955 was 10955, checked in by charles, 12 years ago

(trunk T) #3045 "units" -- modify the formatter functions based on feedback from BMW

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