PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. /*
  2. Copyright (c) 2007 Volker Krause <vkrause@kde.org>
  3. This library is free software; you can redistribute it and/or modify it
  4. under the terms of the GNU Library General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or (at your
  6. option) any later version.
  7. This library is distributed in the hope that it will be useful, but WITHOUT
  8. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
  10. License for more details.
  11. You should have received a copy of the GNU Library General Public License
  12. along with this library; see the file COPYING.LIB. If not, write to the
  13. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  14. 02110-1301, USA.
  15. */
  16. #include "session.h"
  17. #include "session_p.h"
  18. #include "imapparser_p.h"
  19. #include "job.h"
  20. #include "job_p.h"
  21. #include "servermanager.h"
  22. #include "servermanager_p.h"
  23. #include "xdgbasedirs_p.h"
  24. #include <kdebug.h>
  25. #include <klocale.h>
  26. #include <QCoreApplication>
  27. #include <QtCore/QDir>
  28. #include <QtCore/QQueue>
  29. #include <QtCore/QThreadStorage>
  30. #include <QtCore/QTimer>
  31. #include <QtCore/QThread>
  32. #include <QSettings>
  33. #include <QtNetwork/QLocalSocket>
  34. #include <QtNetwork/QTcpSocket>
  35. #include <QtNetwork/QHostAddress>
  36. // ### FIXME pipelining got broken by switching result emission in JobPrivate::handleResponse to delayed emission
  37. // in order to work around exec() deadlocks. As a result of that Session knows to late about a finished job and still
  38. // sends responses for the next one to the already finished one
  39. #define PIPELINE_LENGTH 0
  40. //#define PIPELINE_LENGTH 2
  41. using namespace Akonadi;
  42. //@cond PRIVATE
  43. void SessionPrivate::startNext()
  44. {
  45. QTimer::singleShot( 0, mParent, SLOT(doStartNext()) );
  46. }
  47. void SessionPrivate::reconnect()
  48. {
  49. QLocalSocket *localSocket = qobject_cast<QLocalSocket*>( socket );
  50. if ( localSocket && (localSocket->state() == QLocalSocket::ConnectedState
  51. || localSocket->state() == QLocalSocket::ConnectingState ) ) {
  52. // nothing to do, we are still/already connected
  53. return;
  54. }
  55. QTcpSocket *tcpSocket = qobject_cast<QTcpSocket*>( socket );
  56. if ( tcpSocket && (tcpSocket->state() == QTcpSocket::ConnectedState
  57. || tcpSocket->state() == QTcpSocket::ConnectingState ) ) {
  58. // same here, but for TCP
  59. return;
  60. }
  61. // try to figure out where to connect to
  62. QString serverAddress;
  63. quint16 port = 0;
  64. bool useTcp = false;
  65. // env var has precedence
  66. const QByteArray serverAddressEnvVar = qgetenv( "AKONADI_SERVER_ADDRESS" );
  67. if ( !serverAddressEnvVar.isEmpty() ) {
  68. const int pos = serverAddressEnvVar.indexOf( ':' );
  69. const QByteArray protocol = serverAddressEnvVar.left( pos );
  70. QMap<QString, QString> options;
  71. foreach ( const QString &entry, QString::fromLatin1( serverAddressEnvVar.mid( pos + 1 ) ).split( QLatin1Char(',') ) ) {
  72. const QStringList pair = entry.split( QLatin1Char('=') );
  73. if ( pair.size() != 2 )
  74. continue;
  75. options.insert( pair.first(), pair.last() );
  76. }
  77. kDebug() << protocol << options;
  78. if ( protocol == "tcp" ) {
  79. serverAddress = options.value( QLatin1String( "host" ) );
  80. port = options.value( QLatin1String( "port" ) ).toUInt();
  81. useTcp = true;
  82. } else if ( protocol == "unix" ) {
  83. serverAddress = options.value( QLatin1String( "path" ) );
  84. } else if ( protocol == "pipe" ) {
  85. serverAddress = options.value( QLatin1String( "name" ) );
  86. }
  87. }
  88. // try config file next, fall back to defaults if that fails as well
  89. if ( serverAddress.isEmpty() ) {
  90. const QString connectionConfigFile = XdgBaseDirs::akonadiConnectionConfigFile();
  91. const QFileInfo fileInfo( connectionConfigFile );
  92. if ( !fileInfo.exists() ) {
  93. kDebug() << "Akonadi Client Session: connection config file '"
  94. "akonadi/akonadiconnectionrc' can not be found in"
  95. << XdgBaseDirs::homePath( "config" ) << "nor in any of"
  96. << XdgBaseDirs::systemPathList( "config" );
  97. }
  98. const QSettings connectionSettings( connectionConfigFile, QSettings::IniFormat );
  99. #ifdef Q_OS_WIN //krazy:exclude=cpp
  100. serverAddress = connectionSettings.value( QLatin1String( "Data/NamedPipe" ), QLatin1String( "Akonadi" ) ).toString();
  101. #else
  102. const QString defaultSocketDir = XdgBaseDirs::saveDir( "data", QLatin1String( "akonadi" ) );
  103. serverAddress = connectionSettings.value( QLatin1String( "Data/UnixPath" ), QString(defaultSocketDir + QLatin1String( "/akonadiserver.socket" )) ).toString();
  104. #endif
  105. }
  106. #ifdef Q_OS_WINCE
  107. useTcp = true;
  108. #endif
  109. // create sockets if not yet done, note that this does not yet allow changing socket types on the fly
  110. // but that's probably not something we need to support anyway
  111. if ( !socket ) {
  112. if ( !useTcp ) {
  113. socket = localSocket = new QLocalSocket( mParent );
  114. mParent->connect( localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), SLOT(socketError(QLocalSocket::LocalSocketError)) );
  115. } else {
  116. socket = tcpSocket = new QTcpSocket( mParent );
  117. mParent->connect( tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)) );
  118. }
  119. mParent->connect( socket, SIGNAL(disconnected()), SLOT(socketDisconnected()) );
  120. mParent->connect( socket, SIGNAL(readyRead()), SLOT(dataReceived()) );
  121. }
  122. // actually do connect
  123. kDebug() << "connectToServer" << serverAddress;
  124. #ifdef Q_OS_WINCE
  125. tcpSocket->connectToHost( QHostAddress::LocalHost, 31414 );
  126. #else
  127. if ( !useTcp ) {
  128. localSocket->connectToServer( serverAddress );
  129. } else {
  130. tcpSocket->connectToHost( serverAddress, port );
  131. }
  132. #endif
  133. emit mParent->reconnected();
  134. }
  135. void SessionPrivate::socketError( QLocalSocket::LocalSocketError )
  136. {
  137. Q_ASSERT( mParent->sender() == socket );
  138. kWarning() << "Socket error occurred:" << qobject_cast<QLocalSocket*>( socket )->errorString();
  139. socketDisconnected();
  140. }
  141. void SessionPrivate::socketError( QAbstractSocket::SocketError )
  142. {
  143. Q_ASSERT( mParent->sender() == socket );
  144. kWarning() << "Socket error occurred:" << qobject_cast<QTcpSocket*>( socket )->errorString();
  145. socketDisconnected();
  146. }
  147. void SessionPrivate::socketDisconnected()
  148. {
  149. if ( currentJob )
  150. currentJob->d_ptr->lostConnection();
  151. connected = false;
  152. }
  153. void SessionPrivate::dataReceived()
  154. {
  155. while ( socket->bytesAvailable() > 0 ) {
  156. if ( parser->continuationSize() > 1 ) {
  157. const QByteArray data = socket->read( qMin( socket->bytesAvailable(), parser->continuationSize() - 1 ) );
  158. parser->parseBlock( data );
  159. } else if ( socket->canReadLine() ) {
  160. if ( !parser->parseNextLine( socket->readLine() ) )
  161. continue; // response not yet completed
  162. // handle login response
  163. if ( parser->tag() == QByteArray( "0" ) ) {
  164. if ( parser->data().startsWith( "OK" ) ) { //krazy:exclude=strings
  165. connected = true;
  166. startNext();
  167. } else {
  168. kWarning() << "Unable to login to Akonadi server:" << parser->data();
  169. socket->close();
  170. QTimer::singleShot( 1000, mParent, SLOT(reconnect()) );
  171. }
  172. }
  173. // send login command
  174. if ( parser->tag() == "*" && parser->data().startsWith( "OK Akonadi" ) ) {
  175. const int pos = parser->data().indexOf( "[PROTOCOL" );
  176. if ( pos > 0 ) {
  177. qint64 tmp = 0;
  178. ImapParser::parseNumber( parser->data(), tmp, 0, pos + 9 );
  179. protocolVersion = tmp;
  180. Internal::setServerProtocolVersion( tmp );
  181. }
  182. kDebug() << "Server protocol version is:" << protocolVersion;
  183. writeData( "0 LOGIN " + ImapParser::quote( sessionId ) + '\n' );
  184. // work for the current job
  185. } else {
  186. if ( currentJob )
  187. currentJob->d_ptr->handleResponse( parser->tag(), parser->data() );
  188. }
  189. // reset parser stuff
  190. parser->reset();
  191. } else {
  192. break; // nothing we can do for now
  193. }
  194. }
  195. }
  196. bool SessionPrivate::canPipelineNext()
  197. {
  198. if ( queue.isEmpty() || pipeline.count() >= PIPELINE_LENGTH )
  199. return false;
  200. if ( pipeline.isEmpty() && currentJob )
  201. return currentJob->d_ptr->mWriteFinished;
  202. if ( !pipeline.isEmpty() )
  203. return pipeline.last()->d_ptr->mWriteFinished;
  204. return false;
  205. }
  206. void SessionPrivate::doStartNext()
  207. {
  208. if ( !connected || (queue.isEmpty() && pipeline.isEmpty()) )
  209. return;
  210. if ( canPipelineNext() ) {
  211. Akonadi::Job *nextJob = queue.dequeue();
  212. pipeline.enqueue( nextJob );
  213. startJob( nextJob );
  214. }
  215. if ( jobRunning )
  216. return;
  217. jobRunning = true;
  218. if ( !pipeline.isEmpty() ) {
  219. currentJob = pipeline.dequeue();
  220. } else {
  221. currentJob = queue.dequeue();
  222. startJob( currentJob );
  223. }
  224. }
  225. void SessionPrivate::startJob( Job *job )
  226. {
  227. if ( protocolVersion < minimumProtocolVersion() ) {
  228. job->setError( Job::ProtocolVersionMismatch );
  229. job->setErrorText( i18n( "Protocol version %1 found, expected at least %2", protocolVersion, minimumProtocolVersion() ) );
  230. job->emitResult();
  231. } else {
  232. job->d_ptr->startQueued();
  233. }
  234. }
  235. void SessionPrivate::endJob( Job *job )
  236. {
  237. job->emitResult();
  238. }
  239. void SessionPrivate::jobDone(KJob * job)
  240. {
  241. // ### careful, this method can be called from the QObject dtor of job (see jobDestroyed() below)
  242. // so don't call any methods on job itself
  243. if ( job == currentJob ) {
  244. if ( pipeline.isEmpty() ) {
  245. jobRunning = false;
  246. currentJob = 0;
  247. } else {
  248. currentJob = pipeline.dequeue();
  249. }
  250. startNext();
  251. } else {
  252. // non-current job finished, likely canceled while still in the queue
  253. queue.removeAll( static_cast<Akonadi::Job*>( job ) );
  254. // ### likely not enough to really cancel already running jobs
  255. pipeline.removeAll( static_cast<Akonadi::Job*>( job ) );
  256. }
  257. }
  258. void SessionPrivate::jobWriteFinished( Akonadi::Job* job )
  259. {
  260. Q_ASSERT( (job == currentJob && pipeline.isEmpty()) || (job = pipeline.last()) );
  261. Q_UNUSED( job );
  262. startNext();
  263. }
  264. void SessionPrivate::jobDestroyed(QObject * job)
  265. {
  266. // careful, accessing non-QObject methods of job will fail here already
  267. jobDone( static_cast<KJob*>( job ) );
  268. }
  269. void SessionPrivate::addJob(Job * job)
  270. {
  271. queue.append( job );
  272. QObject::connect( job, SIGNAL(result(KJob*)), mParent, SLOT(jobDone(KJob*)) );
  273. QObject::connect( job, SIGNAL(writeFinished(Akonadi::Job*)), mParent, SLOT(jobWriteFinished(Akonadi::Job*)) );
  274. QObject::connect( job, SIGNAL(destroyed(QObject*)), mParent, SLOT(jobDestroyed(QObject*)) );
  275. startNext();
  276. }
  277. int SessionPrivate::nextTag()
  278. {
  279. return theNextTag++;
  280. }
  281. void SessionPrivate::writeData(const QByteArray & data)
  282. {
  283. if ( socket )
  284. socket->write( data );
  285. else
  286. kWarning() << "Trying to write while session is disconnected!" << kBacktrace();
  287. }
  288. void SessionPrivate::serverStateChanged( ServerManager::State state )
  289. {
  290. if ( state == ServerManager::Running && !connected )
  291. reconnect();
  292. }
  293. void SessionPrivate::itemRevisionChanged( Akonadi::Item::Id itemId, int oldRevision, int newRevision )
  294. {
  295. // only deal with the queue, for the guys in the pipeline it's too late already anyway
  296. // and they shouldn't have gotten there if they depend on a preceding job anyway.
  297. foreach ( Job *job, queue )
  298. job->d_ptr->updateItemRevision( itemId, oldRevision, newRevision );
  299. }
  300. //@endcond
  301. SessionPrivate::SessionPrivate( Session *parent )
  302. : mParent( parent ), socket( 0 ), protocolVersion( 0 ), currentJob( 0 ), parser( 0 )
  303. {
  304. }
  305. void SessionPrivate::init( const QByteArray &id )
  306. {
  307. kDebug() << id;
  308. parser = new ImapParser();
  309. if ( !id.isEmpty() ) {
  310. sessionId = id;
  311. } else {
  312. sessionId = QCoreApplication::instance()->applicationName().toUtf8()
  313. + '-' + QByteArray::number( qrand() );
  314. }
  315. connected = false;
  316. theNextTag = 1;
  317. jobRunning = false;
  318. if ( ServerManager::state() == ServerManager::NotRunning )
  319. ServerManager::start();
  320. mParent->connect( ServerManager::self(), SIGNAL(stateChanged(Akonadi::ServerManager::State)),
  321. SLOT(serverStateChanged(Akonadi::ServerManager::State)) );
  322. reconnect();
  323. }
  324. Session::Session(const QByteArray & sessionId, QObject * parent) :
  325. QObject( parent ),
  326. d( new SessionPrivate( this ) )
  327. {
  328. d->init( sessionId );
  329. }
  330. Session::Session( SessionPrivate *dd, const QByteArray & sessionId, QObject * parent)
  331. : QObject( parent ),
  332. d( dd )
  333. {
  334. d->init( sessionId );
  335. }
  336. Session::~Session()
  337. {
  338. clear();
  339. delete d;
  340. }
  341. QByteArray Session::sessionId() const
  342. {
  343. return d->sessionId;
  344. }
  345. static QThreadStorage<Session*> instances;
  346. void SessionPrivate::createDefaultSession( const QByteArray &sessionId )
  347. {
  348. Q_ASSERT_X( !sessionId.isEmpty(), "SessionPrivate::createDefaultSession",
  349. "You tried to create a default session with empty session id!" );
  350. Q_ASSERT_X( !instances.hasLocalData(), "SessionPrivate::createDefaultSession",
  351. "You tried to create a default session twice!" );
  352. instances.setLocalData( new Session( sessionId ) );
  353. }
  354. Session* Session::defaultSession()
  355. {
  356. if ( !instances.hasLocalData() )
  357. instances.setLocalData( new Session() );
  358. return instances.localData();
  359. }
  360. void Session::clear()
  361. {
  362. foreach ( Job* job, d->queue )
  363. job->kill( KJob::EmitResult );
  364. d->queue.clear();
  365. foreach ( Job* job, d->pipeline )
  366. job->kill( KJob::EmitResult );
  367. d->pipeline.clear();
  368. if ( d->currentJob )
  369. d->currentJob->kill( KJob::EmitResult );
  370. d->jobRunning = false;
  371. d->connected = false;
  372. if ( d->socket )
  373. d->socket->disconnect( this ); // prevent signal emitted from close() causing mayhem - we might be called from ~QThreadStorage!
  374. delete d->socket;
  375. d->socket = 0;
  376. QMetaObject::invokeMethod( this, "reconnect", Qt::QueuedConnection ); // avoids reconnecting in the dtor
  377. }
  378. #include "session.moc"