395 lines
13 KiB
C++
395 lines
13 KiB
C++
/*
|
|
* This file Copyright (C) 2009-2010 Mnemosyne LLC
|
|
*
|
|
* This file is licensed by the GPL version 2. Works owned by the
|
|
* Transmission project are granted a special exemption to clause 2(b)
|
|
* so that the bulk of its code can remain under the MIT license.
|
|
* This exemption does not extend to derived works not owned by
|
|
* the Transmission project.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <ctime>
|
|
#include <iostream>
|
|
|
|
#include <QDBusConnection>
|
|
#include <QDBusError>
|
|
#include <QDBusMessage>
|
|
#include <QDialogButtonBox>
|
|
#include <QIcon>
|
|
#include <QLabel>
|
|
#include <QLibraryInfo>
|
|
#include <QRect>
|
|
#include <QTranslator>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/tr-getopt.h>
|
|
#include <libtransmission/version.h>
|
|
|
|
#include "app.h"
|
|
#include "dbus-adaptor.h"
|
|
#include "mainwin.h"
|
|
#include "options.h"
|
|
#include "prefs.h"
|
|
#include "session.h"
|
|
#include "session-dialog.h"
|
|
#include "torrent-model.h"
|
|
#include "utils.h"
|
|
#include "watchdir.h"
|
|
|
|
namespace
|
|
{
|
|
const char * DBUS_SERVICE ( "com.transmissionbt.Transmission" );
|
|
const char * DBUS_OBJECT_PATH ( "/com/transmissionbt/Transmission" );
|
|
const char * DBUS_INTERFACE ( "com.transmissionbt.Transmission" );
|
|
|
|
const char * MY_NAME( "transmission" );
|
|
|
|
const tr_option opts[] =
|
|
{
|
|
{ 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
|
|
{ 'm', "minimized", "Start minimized in system tray", "m", 0, NULL },
|
|
{ 'p', "port", "Port to use when connecting to an existing session", "p", 1, "<port>" },
|
|
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", 1, "<host>" },
|
|
{ 'u', "username", "Username to use when connecting to an existing session", "v", 1, "<username>" },
|
|
{ 'v', "version", "Show version number and exit", "v", 0, NULL },
|
|
{ 'w', "password", "Password to use when connecting to an existing session", "w", 1, "<password>" },
|
|
{ 0, NULL, NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
const char*
|
|
getUsage( void )
|
|
{
|
|
return "Usage:\n"
|
|
" transmission [OPTIONS...] [torrent files]";
|
|
}
|
|
|
|
void
|
|
showUsage( void )
|
|
{
|
|
tr_getopt_usage( MY_NAME, getUsage( ), opts );
|
|
exit( 0 );
|
|
}
|
|
|
|
enum
|
|
{
|
|
STATS_REFRESH_INTERVAL_MSEC = 3000,
|
|
SESSION_REFRESH_INTERVAL_MSEC = 3000,
|
|
MODEL_REFRESH_INTERVAL_MSEC = 3000
|
|
};
|
|
}
|
|
|
|
MyApp :: MyApp( int& argc, char ** argv ):
|
|
QApplication( argc, argv ),
|
|
myLastFullUpdateTime( 0 )
|
|
{
|
|
setApplicationName( MY_NAME );
|
|
|
|
// install the qt translator
|
|
QTranslator * t = new QTranslator( );
|
|
t->load( "qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
|
|
installTranslator( t );
|
|
|
|
// install the transmission translator
|
|
t = new QTranslator( );
|
|
t->load( QString(MY_NAME) + "_" + QLocale::system().name() );
|
|
installTranslator( t );
|
|
|
|
// set the default icon
|
|
QIcon icon;
|
|
icon.addPixmap( QPixmap( ":/icons/transmission-16.png" ) );
|
|
icon.addPixmap( QPixmap( ":/icons/transmission-22.png" ) );
|
|
icon.addPixmap( QPixmap( ":/icons/transmission-24.png" ) );
|
|
icon.addPixmap( QPixmap( ":/icons/transmission-32.png" ) );
|
|
icon.addPixmap( QPixmap( ":/icons/transmission-48.png" ) );
|
|
setWindowIcon( icon );
|
|
|
|
// parse the command-line arguments
|
|
int c;
|
|
bool minimized = false;
|
|
const char * optarg;
|
|
const char * host = 0;
|
|
const char * port = 0;
|
|
const char * username = 0;
|
|
const char * password = 0;
|
|
const char * configDir = 0;
|
|
QStringList filenames;
|
|
while( ( c = tr_getopt( getUsage( ), argc, (const char**)argv, opts, &optarg ) ) ) {
|
|
switch( c ) {
|
|
case 'g': configDir = optarg; break;
|
|
case 'p': port = optarg; break;
|
|
case 'r': host = optarg; break;
|
|
case 'u': username = optarg; break;
|
|
case 'w': password = optarg; break;
|
|
case 'm': minimized = true; break;
|
|
case 'v': Utils::toStderr( QObject::tr( "transmission %1" ).arg( LONG_VERSION_STRING ) ); exit( 0 ); break;
|
|
case TR_OPT_ERR: Utils::toStderr( QObject::tr( "Invalid option" ) ); showUsage( ); break;
|
|
default: filenames.append( optarg ); break;
|
|
}
|
|
}
|
|
|
|
// set the fallback config dir
|
|
if( configDir == 0 )
|
|
configDir = tr_getDefaultConfigDir( MY_NAME );
|
|
|
|
// is this the first time we've run transmission?
|
|
const bool firstTime = !QFile(QDir(configDir).absoluteFilePath("settings.json")).exists();
|
|
|
|
// initialize the prefs
|
|
myPrefs = new Prefs ( configDir );
|
|
if( host != 0 )
|
|
myPrefs->set( Prefs::SESSION_REMOTE_HOST, host );
|
|
if( port != 0 )
|
|
myPrefs->set( Prefs::SESSION_REMOTE_PORT, port );
|
|
if( username != 0 )
|
|
myPrefs->set( Prefs::SESSION_REMOTE_USERNAME, username );
|
|
if( password != 0 )
|
|
myPrefs->set( Prefs::SESSION_REMOTE_PASSWORD, password );
|
|
if( ( host != 0 ) || ( port != 0 ) || ( username != 0 ) || ( password != 0 ) )
|
|
myPrefs->set( Prefs::SESSION_IS_REMOTE, true );
|
|
|
|
mySession = new Session( configDir, *myPrefs );
|
|
myModel = new TorrentModel( *myPrefs );
|
|
myWindow = new TrMainWindow( *mySession, *myPrefs, *myModel, minimized );
|
|
myWatchDir = new WatchDir( *myModel );
|
|
|
|
// when the session gets torrent info, update the model
|
|
connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myModel, SLOT(updateTorrents(tr_benc*,bool)) );
|
|
connect( mySession, SIGNAL(torrentsUpdated(tr_benc*,bool)), myWindow, SLOT(refreshActionSensitivity()) );
|
|
connect( mySession, SIGNAL(torrentsRemoved(tr_benc*)), myModel, SLOT(removeTorrents(tr_benc*)) );
|
|
// when the model sees a torrent for the first time, ask the session for full info on it
|
|
connect( myModel, SIGNAL(torrentsAdded(QSet<int>)), mySession, SLOT(initTorrents(QSet<int>)) );
|
|
|
|
mySession->initTorrents( );
|
|
mySession->refreshSessionStats( );
|
|
|
|
// when torrents are added to the watch directory, tell the session
|
|
connect( myWatchDir, SIGNAL(torrentFileAdded(QString)), this, SLOT(addTorrent(QString)) );
|
|
|
|
// init from preferences
|
|
QList<int> initKeys;
|
|
initKeys << Prefs::DIR_WATCH;
|
|
foreach( int key, initKeys )
|
|
refreshPref( key );
|
|
connect( myPrefs, SIGNAL(changed(int)), this, SLOT(refreshPref(const int)) );
|
|
|
|
QTimer * timer = &myModelTimer;
|
|
connect( timer, SIGNAL(timeout()), this, SLOT(refreshTorrents()) );
|
|
timer->setSingleShot( false );
|
|
timer->setInterval( MODEL_REFRESH_INTERVAL_MSEC );
|
|
timer->start( );
|
|
|
|
timer = &myStatsTimer;
|
|
connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionStats()) );
|
|
timer->setSingleShot( false );
|
|
timer->setInterval( STATS_REFRESH_INTERVAL_MSEC );
|
|
timer->start( );
|
|
|
|
timer = &mySessionTimer;
|
|
connect( timer, SIGNAL(timeout()), mySession, SLOT(refreshSessionInfo()) );
|
|
timer->setSingleShot( false );
|
|
timer->setInterval( SESSION_REFRESH_INTERVAL_MSEC );
|
|
timer->start( );
|
|
|
|
maybeUpdateBlocklist( );
|
|
|
|
if( !firstTime )
|
|
mySession->restart( );
|
|
else {
|
|
QDialog * d = new SessionDialog( *mySession, *myPrefs, myWindow );
|
|
d->show( );
|
|
}
|
|
|
|
if( !myPrefs->getBool( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT ))
|
|
{
|
|
QDialog * dialog = new QDialog( myWindow );
|
|
dialog->setModal( true );
|
|
QVBoxLayout * v = new QVBoxLayout( dialog );
|
|
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. And of course, any content you share is your sole responsibility.\n\nYou probably knew this, so we won't tell you again." ) );
|
|
l->setWordWrap( true );
|
|
v->addWidget( l );
|
|
QDialogButtonBox * box = new QDialogButtonBox;
|
|
box->addButton( new QPushButton( tr( "&Cancel" ) ), QDialogButtonBox::RejectRole );
|
|
QPushButton * agree = new QPushButton( tr( "I &Agree" ) );
|
|
agree->setDefault( true );
|
|
box->addButton( agree, QDialogButtonBox::AcceptRole );
|
|
box->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
|
|
box->setOrientation( Qt::Horizontal );
|
|
v->addWidget( box );
|
|
connect( box, SIGNAL(rejected()), this, SLOT(quit()) );
|
|
connect( box, SIGNAL(accepted()), dialog, SLOT(deleteLater()) );
|
|
connect( box, SIGNAL(accepted()), this, SLOT(consentGiven()) );
|
|
dialog->show();
|
|
}
|
|
|
|
for( QStringList::const_iterator it=filenames.begin(), end=filenames.end(); it!=end; ++it )
|
|
mySession->addTorrent( *it );
|
|
|
|
// register as the dbus handler for Transmission
|
|
new TrDBusAdaptor( this );
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (!bus.registerService("com.transmissionbt.Transmission"))
|
|
fprintf(stderr, "%s\n", qPrintable(bus.lastError().message()));
|
|
if( !bus.registerObject( "/com/transmissionbt/Transmission", this ))
|
|
fprintf(stderr, "%s\n", qPrintable(bus.lastError().message()));
|
|
}
|
|
|
|
void
|
|
MyApp :: consentGiven( )
|
|
{
|
|
myPrefs->set<bool>( Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true );
|
|
}
|
|
|
|
MyApp :: ~MyApp( )
|
|
{
|
|
const QRect mainwinRect( myWindow->geometry( ) );
|
|
delete myWatchDir;
|
|
delete myWindow;
|
|
delete myModel;
|
|
delete mySession;
|
|
|
|
myPrefs->set( Prefs :: MAIN_WINDOW_HEIGHT, std::max( 100, mainwinRect.height( ) ) );
|
|
myPrefs->set( Prefs :: MAIN_WINDOW_WIDTH, std::max( 100, mainwinRect.width( ) ) );
|
|
myPrefs->set( Prefs :: MAIN_WINDOW_X, mainwinRect.x( ) );
|
|
myPrefs->set( Prefs :: MAIN_WINDOW_Y, mainwinRect.y( ) );
|
|
delete myPrefs;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MyApp :: refreshPref( int key )
|
|
{
|
|
switch( key )
|
|
{
|
|
case Prefs :: BLOCKLIST_UPDATES_ENABLED:
|
|
maybeUpdateBlocklist( );
|
|
break;
|
|
|
|
case Prefs :: DIR_WATCH:
|
|
case Prefs :: DIR_WATCH_ENABLED: {
|
|
const QString path( myPrefs->getString( Prefs::DIR_WATCH ) );
|
|
const bool isEnabled( myPrefs->getBool( Prefs::DIR_WATCH_ENABLED ) );
|
|
myWatchDir->setPath( path, isEnabled );
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
MyApp :: maybeUpdateBlocklist( )
|
|
{
|
|
if( !myPrefs->getBool( Prefs :: BLOCKLIST_UPDATES_ENABLED ) )
|
|
return;
|
|
|
|
const QDateTime lastUpdatedAt = myPrefs->getDateTime( Prefs :: BLOCKLIST_DATE );
|
|
const QDateTime nextUpdateAt = lastUpdatedAt.addDays( 7 );
|
|
const QDateTime now = QDateTime::currentDateTime( );
|
|
if( now < nextUpdateAt )
|
|
{
|
|
mySession->updateBlocklist( );
|
|
myPrefs->set( Prefs :: BLOCKLIST_DATE, now );
|
|
}
|
|
}
|
|
|
|
void
|
|
MyApp :: refreshTorrents( )
|
|
{
|
|
// usually we just poll the torrents that have shown recent activity,
|
|
// but we also periodically ask for updates on the others to ensure
|
|
// nothing's falling through the cracks.
|
|
const time_t now = time( NULL );
|
|
if( myLastFullUpdateTime + 60 >= now )
|
|
mySession->refreshActiveTorrents( );
|
|
else {
|
|
myLastFullUpdateTime = now;
|
|
mySession->refreshAllTorrents( );
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void
|
|
MyApp :: addTorrent( const QString& key )
|
|
{
|
|
if( !myPrefs->getBool( Prefs :: OPTIONS_PROMPT ) )
|
|
mySession->addTorrent( key );
|
|
else if( !QFile( key ).exists( ) )
|
|
myWindow->openURL( key );
|
|
else {
|
|
Options * o = new Options( *mySession, *myPrefs, key, myWindow );
|
|
o->show( );
|
|
}
|
|
raise( );
|
|
}
|
|
|
|
void
|
|
MyApp :: raise( )
|
|
{
|
|
QApplication :: alert ( myWindow );
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
int
|
|
main( int argc, char * argv[] )
|
|
{
|
|
// find .torrents, URLs, magnet links, etc in the command-line args
|
|
int c;
|
|
QStringList addme;
|
|
const char * optarg;
|
|
char ** argvv = argv;
|
|
while( ( c = tr_getopt( getUsage( ), argc, (const char **)argvv, opts, &optarg ) ) )
|
|
if( c == TR_OPT_UNK )
|
|
addme.append( optarg );
|
|
|
|
// try to delegate the work to an existing copy of Transmission
|
|
// before starting ourselves...
|
|
bool delegated = false;
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
for( int i=0, n=addme.size(); i<n; ++i )
|
|
{
|
|
const QString key = addme[i];
|
|
|
|
QDBusMessage request = QDBusMessage::createMethodCall( DBUS_SERVICE,
|
|
DBUS_OBJECT_PATH,
|
|
DBUS_INTERFACE,
|
|
"AddMetainfo" );
|
|
QList<QVariant> arguments;
|
|
arguments.push_back( QVariant( key ) );
|
|
request.setArguments( arguments );
|
|
|
|
QDBusMessage response = bus.call( request );
|
|
arguments = response.arguments( );
|
|
delegated |= (arguments.size()==1) && arguments[0].toBool();
|
|
}
|
|
if( addme.empty() )
|
|
{
|
|
QDBusMessage request = QDBusMessage::createMethodCall( DBUS_SERVICE,
|
|
DBUS_OBJECT_PATH,
|
|
DBUS_INTERFACE,
|
|
"PresentWindow" );
|
|
QDBusMessage response = bus.call( request );
|
|
QList<QVariant> arguments = response.arguments( );
|
|
delegated |= (arguments.size()==1) && arguments[0].toBool();
|
|
}
|
|
|
|
if( delegated )
|
|
return 0;
|
|
|
|
tr_optind = 1;
|
|
MyApp app( argc, argv );
|
|
return app.exec( );
|
|
}
|