/kdepimlibs-4.8.97/akonadi/session.cpp
C++ | 441 lines | 338 code | 60 blank | 43 comment | 80 complexity | dddbef101ac153b16f32f348ada713fb MD5 | raw file
Possible License(s): LGPL-2.0, BSD-2-Clause, GPL-2.0, LGPL-2.1
- /*
- Copyright (c) 2007 Volker Krause <vkrause@kde.org>
- This library is free software; you can redistribute it and/or modify it
- under the terms of the GNU Library General Public License as published by
- the Free Software Foundation; either version 2 of the License, or (at your
- option) any later version.
- This library is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
- License for more details.
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA.
- */
- #include "session.h"
- #include "session_p.h"
- #include "imapparser_p.h"
- #include "job.h"
- #include "job_p.h"
- #include "servermanager.h"
- #include "servermanager_p.h"
- #include "xdgbasedirs_p.h"
- #include <kdebug.h>
- #include <klocale.h>
- #include <QCoreApplication>
- #include <QtCore/QDir>
- #include <QtCore/QQueue>
- #include <QtCore/QThreadStorage>
- #include <QtCore/QTimer>
- #include <QtCore/QThread>
- #include <QSettings>
- #include <QtNetwork/QLocalSocket>
- #include <QtNetwork/QTcpSocket>
- #include <QtNetwork/QHostAddress>
- // ### FIXME pipelining got broken by switching result emission in JobPrivate::handleResponse to delayed emission
- // in order to work around exec() deadlocks. As a result of that Session knows to late about a finished job and still
- // sends responses for the next one to the already finished one
- #define PIPELINE_LENGTH 0
- //#define PIPELINE_LENGTH 2
- using namespace Akonadi;
- //@cond PRIVATE
- void SessionPrivate::startNext()
- {
- QTimer::singleShot( 0, mParent, SLOT(doStartNext()) );
- }
- void SessionPrivate::reconnect()
- {
- QLocalSocket *localSocket = qobject_cast<QLocalSocket*>( socket );
- if ( localSocket && (localSocket->state() == QLocalSocket::ConnectedState
- || localSocket->state() == QLocalSocket::ConnectingState ) ) {
- // nothing to do, we are still/already connected
- return;
- }
- QTcpSocket *tcpSocket = qobject_cast<QTcpSocket*>( socket );
- if ( tcpSocket && (tcpSocket->state() == QTcpSocket::ConnectedState
- || tcpSocket->state() == QTcpSocket::ConnectingState ) ) {
- // same here, but for TCP
- return;
- }
- // try to figure out where to connect to
- QString serverAddress;
- quint16 port = 0;
- bool useTcp = false;
- // env var has precedence
- const QByteArray serverAddressEnvVar = qgetenv( "AKONADI_SERVER_ADDRESS" );
- if ( !serverAddressEnvVar.isEmpty() ) {
- const int pos = serverAddressEnvVar.indexOf( ':' );
- const QByteArray protocol = serverAddressEnvVar.left( pos );
- QMap<QString, QString> options;
- foreach ( const QString &entry, QString::fromLatin1( serverAddressEnvVar.mid( pos + 1 ) ).split( QLatin1Char(',') ) ) {
- const QStringList pair = entry.split( QLatin1Char('=') );
- if ( pair.size() != 2 )
- continue;
- options.insert( pair.first(), pair.last() );
- }
- kDebug() << protocol << options;
- if ( protocol == "tcp" ) {
- serverAddress = options.value( QLatin1String( "host" ) );
- port = options.value( QLatin1String( "port" ) ).toUInt();
- useTcp = true;
- } else if ( protocol == "unix" ) {
- serverAddress = options.value( QLatin1String( "path" ) );
- } else if ( protocol == "pipe" ) {
- serverAddress = options.value( QLatin1String( "name" ) );
- }
- }
- // try config file next, fall back to defaults if that fails as well
- if ( serverAddress.isEmpty() ) {
- const QString connectionConfigFile = XdgBaseDirs::akonadiConnectionConfigFile();
- const QFileInfo fileInfo( connectionConfigFile );
- if ( !fileInfo.exists() ) {
- kDebug() << "Akonadi Client Session: connection config file '"
- "akonadi/akonadiconnectionrc' can not be found in"
- << XdgBaseDirs::homePath( "config" ) << "nor in any of"
- << XdgBaseDirs::systemPathList( "config" );
- }
- const QSettings connectionSettings( connectionConfigFile, QSettings::IniFormat );
- #ifdef Q_OS_WIN //krazy:exclude=cpp
- serverAddress = connectionSettings.value( QLatin1String( "Data/NamedPipe" ), QLatin1String( "Akonadi" ) ).toString();
- #else
- const QString defaultSocketDir = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) );
- serverAddress = connectionSettings.value( QLatin1String( "Data/UnixPath" ), QString(defaultSocketDir + QLatin1String( "/akonadiserver.socket" )) ).toString();
- #endif
- }
- #ifdef Q_OS_WINCE
- useTcp = true;
- #endif
- // create sockets if not yet done, note that this does not yet allow changing socket types on the fly
- // but that's probably not something we need to support anyway
- if ( !socket ) {
- if ( !useTcp ) {
- socket = localSocket = new QLocalSocket( mParent );
- mParent->connect( localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), SLOT(socketError(QLocalSocket::LocalSocketError)) );
- } else {
- socket = tcpSocket = new QTcpSocket( mParent );
- mParent->connect( tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)) );
- }
- mParent->connect( socket, SIGNAL(disconnected()), SLOT(socketDisconnected()) );
- mParent->connect( socket, SIGNAL(readyRead()), SLOT(dataReceived()) );
- }
- // actually do connect
- kDebug() << "connectToServer" << serverAddress;
- #ifdef Q_OS_WINCE
- tcpSocket->connectToHost( QHostAddress::LocalHost, 31414 );
- #else
- if ( !useTcp ) {
- localSocket->connectToServer( serverAddress );
- } else {
- tcpSocket->connectToHost( serverAddress, port );
- }
- #endif
- emit mParent->reconnected();
- }
- void SessionPrivate::socketError( QLocalSocket::LocalSocketError )
- {
- Q_ASSERT( mParent->sender() == socket );
- kWarning() << "Socket error occurred:" << qobject_cast<QLocalSocket*>( socket )->errorString();
- socketDisconnected();
- }
- void SessionPrivate::socketError( QAbstractSocket::SocketError )
- {
- Q_ASSERT( mParent->sender() == socket );
- kWarning() << "Socket error occurred:" << qobject_cast<QTcpSocket*>( socket )->errorString();
- socketDisconnected();
- }
- void SessionPrivate::socketDisconnected()
- {
- if ( currentJob )
- currentJob->d_ptr->lostConnection();
- connected = false;
- }
- void SessionPrivate::dataReceived()
- {
- while ( socket->bytesAvailable() > 0 ) {
- if ( parser->continuationSize() > 1 ) {
- const QByteArray data = socket->read( qMin( socket->bytesAvailable(), parser->continuationSize() - 1 ) );
- parser->parseBlock( data );
- } else if ( socket->canReadLine() ) {
- if ( !parser->parseNextLine( socket->readLine() ) )
- continue; // response not yet completed
- // handle login response
- if ( parser->tag() == QByteArray( "0" ) ) {
- if ( parser->data().startsWith( "OK" ) ) { //krazy:exclude=strings
- connected = true;
- startNext();
- } else {
- kWarning() << "Unable to login to Akonadi server:" << parser->data();
- socket->close();
- QTimer::singleShot( 1000, mParent, SLOT(reconnect()) );
- }
- }
- // send login command
- if ( parser->tag() == "*" && parser->data().startsWith( "OK Akonadi" ) ) {
- const int pos = parser->data().indexOf( "[PROTOCOL" );
- if ( pos > 0 ) {
- qint64 tmp = 0;
- ImapParser::parseNumber( parser->data(), tmp, 0, pos + 9 );
- protocolVersion = tmp;
- Internal::setServerProtocolVersion( tmp );
- }
- kDebug() << "Server protocol version is:" << protocolVersion;
- writeData( "0 LOGIN " + ImapParser::quote( sessionId ) + '\n' );
- // work for the current job
- } else {
- if ( currentJob )
- currentJob->d_ptr->handleResponse( parser->tag(), parser->data() );
- }
- // reset parser stuff
- parser->reset();
- } else {
- break; // nothing we can do for now
- }
- }
- }
- bool SessionPrivate::canPipelineNext()
- {
- if ( queue.isEmpty() || pipeline.count() >= PIPELINE_LENGTH )
- return false;
- if ( pipeline.isEmpty() && currentJob )
- return currentJob->d_ptr->mWriteFinished;
- if ( !pipeline.isEmpty() )
- return pipeline.last()->d_ptr->mWriteFinished;
- return false;
- }
- void SessionPrivate::doStartNext()
- {
- if ( !connected || (queue.isEmpty() && pipeline.isEmpty()) )
- return;
- if ( canPipelineNext() ) {
- Akonadi::Job *nextJob = queue.dequeue();
- pipeline.enqueue( nextJob );
- startJob( nextJob );
- }
- if ( jobRunning )
- return;
- jobRunning = true;
- if ( !pipeline.isEmpty() ) {
- currentJob = pipeline.dequeue();
- } else {
- currentJob = queue.dequeue();
- startJob( currentJob );
- }
- }
- void SessionPrivate::startJob( Job *job )
- {
- if ( protocolVersion < minimumProtocolVersion() ) {
- job->setError( Job::ProtocolVersionMismatch );
- job->setErrorText( i18n( "Protocol version %1 found, expected at least %2", protocolVersion, minimumProtocolVersion() ) );
- job->emitResult();
- } else {
- job->d_ptr->startQueued();
- }
- }
- void SessionPrivate::endJob( Job *job )
- {
- job->emitResult();
- }
- void SessionPrivate::jobDone(KJob * job)
- {
- // ### careful, this method can be called from the QObject dtor of job (see jobDestroyed() below)
- // so don't call any methods on job itself
- if ( job == currentJob ) {
- if ( pipeline.isEmpty() ) {
- jobRunning = false;
- currentJob = 0;
- } else {
- currentJob = pipeline.dequeue();
- }
- startNext();
- } else {
- // non-current job finished, likely canceled while still in the queue
- queue.removeAll( static_cast<Akonadi::Job*>( job ) );
- // ### likely not enough to really cancel already running jobs
- pipeline.removeAll( static_cast<Akonadi::Job*>( job ) );
- }
- }
- void SessionPrivate::jobWriteFinished( Akonadi::Job* job )
- {
- Q_ASSERT( (job == currentJob && pipeline.isEmpty()) || (job = pipeline.last()) );
- Q_UNUSED( job );
- startNext();
- }
- void SessionPrivate::jobDestroyed(QObject * job)
- {
- // careful, accessing non-QObject methods of job will fail here already
- jobDone( static_cast<KJob*>( job ) );
- }
- void SessionPrivate::addJob(Job * job)
- {
- queue.append( job );
- QObject::connect( job, SIGNAL(result(KJob*)), mParent, SLOT(jobDone(KJob*)) );
- QObject::connect( job, SIGNAL(writeFinished(Akonadi::Job*)), mParent, SLOT(jobWriteFinished(Akonadi::Job*)) );
- QObject::connect( job, SIGNAL(destroyed(QObject*)), mParent, SLOT(jobDestroyed(QObject*)) );
- startNext();
- }
- int SessionPrivate::nextTag()
- {
- return theNextTag++;
- }
- void SessionPrivate::writeData(const QByteArray & data)
- {
- if ( socket )
- socket->write( data );
- else
- kWarning() << "Trying to write while session is disconnected!" << kBacktrace();
- }
- void SessionPrivate::serverStateChanged( ServerManager::State state )
- {
- if ( state == ServerManager::Running && !connected )
- reconnect();
- }
- void SessionPrivate::itemRevisionChanged( Akonadi::Item::Id itemId, int oldRevision, int newRevision )
- {
- // only deal with the queue, for the guys in the pipeline it's too late already anyway
- // and they shouldn't have gotten there if they depend on a preceding job anyway.
- foreach ( Job *job, queue )
- job->d_ptr->updateItemRevision( itemId, oldRevision, newRevision );
- }
- //@endcond
- SessionPrivate::SessionPrivate( Session *parent )
- : mParent( parent ), socket( 0 ), protocolVersion( 0 ), currentJob( 0 ), parser( 0 )
- {
- }
- void SessionPrivate::init( const QByteArray &id )
- {
- kDebug() << id;
- parser = new ImapParser();
- if ( !id.isEmpty() ) {
- sessionId = id;
- } else {
- sessionId = QCoreApplication::instance()->applicationName().toUtf8()
- + '-' + QByteArray::number( qrand() );
- }
- connected = false;
- theNextTag = 1;
- jobRunning = false;
- if ( ServerManager::state() == ServerManager::NotRunning )
- ServerManager::start();
- mParent->connect( ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)),
- SLOT(serverStateChanged(Akonadi::ServerManager::State)) );
- reconnect();
- }
- Session::Session(const QByteArray & sessionId, QObject * parent) :
- QObject( parent ),
- d( new SessionPrivate( this ) )
- {
- d->init( sessionId );
- }
- Session::Session( SessionPrivate *dd, const QByteArray & sessionId, QObject * parent)
- : QObject( parent ),
- d( dd )
- {
- d->init( sessionId );
- }
- Session::~Session()
- {
- clear();
- delete d;
- }
- QByteArray Session::sessionId() const
- {
- return d->sessionId;
- }
- static QThreadStorage<Session*> instances;
- void SessionPrivate::createDefaultSession( const QByteArray &sessionId )
- {
- Q_ASSERT_X( !sessionId.isEmpty(), "SessionPrivate::createDefaultSession",
- "You tried to create a default session with empty session id!" );
- Q_ASSERT_X( !instances.hasLocalData(), "SessionPrivate::createDefaultSession",
- "You tried to create a default session twice!" );
- instances.setLocalData( new Session( sessionId ) );
- }
- Session* Session::defaultSession()
- {
- if ( !instances.hasLocalData() )
- instances.setLocalData( new Session() );
- return instances.localData();
- }
- void Session::clear()
- {
- foreach ( Job* job, d->queue )
- job->kill( KJob::EmitResult );
- d->queue.clear();
- foreach ( Job* job, d->pipeline )
- job->kill( KJob::EmitResult );
- d->pipeline.clear();
- if ( d->currentJob )
- d->currentJob->kill( KJob::EmitResult );
- d->jobRunning = false;
- d->connected = false;
- if ( d->socket )
- d->socket->disconnect( this ); // prevent signal emitted from close() causing mayhem - we might be called from ~QThreadStorage!
- delete d->socket;
- d->socket = 0;
- QMetaObject::invokeMethod( this, "reconnect", Qt::QueuedConnection ); // avoids reconnecting in the dtor
- }
- #include "session.moc"