Akonadi

core/connection.cpp
1 /*
2  * SPDX-FileCopyrightText: 2015 Daniel Vr├ítil <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 #include "akonadicore_debug.h"
8 #include "commandbuffer_p.h"
9 #include "connection_p.h"
10 #include "servermanager_p.h"
11 #include "session_p.h"
12 #include <private/instance_p.h>
13 
14 #include <QAbstractEventDispatcher>
15 #include <QApplication>
16 #include <QDateTime>
17 #include <QFile>
18 #include <QFileInfo>
19 #include <QSettings>
20 #include <QTimer>
21 
22 #include <private/datastream_p_p.h>
23 #include <private/protocol_exception_p.h>
24 #include <private/standarddirs_p.h>
25 
26 using namespace Akonadi;
27 
28 Connection::Connection(ConnectionType connType, const QByteArray &sessionId, CommandBuffer *commandBuffer, QObject *parent)
29  : QObject(parent)
30  , mConnectionType(connType)
31  , mSessionId(sessionId)
32  , mCommandBuffer(commandBuffer)
33 {
34  qRegisterMetaType<Protocol::CommandPtr>();
35  qRegisterMetaType<QAbstractSocket::SocketState>();
36 
37  const QByteArray sessionLogFile = qgetenv("AKONADI_SESSION_LOGFILE");
38  if (!sessionLogFile.isEmpty()) {
39  mLogFile = new QFile(QStringLiteral("%1.%2.%3.%4-%5")
40  .arg(QString::fromLatin1(sessionLogFile))
42  .arg(QString::number(reinterpret_cast<qulonglong>(this), 16),
43  QString::fromLatin1(mSessionId.replace('/', '_')),
44  connType == CommandConnection ? QStringLiteral("Cmd") : QStringLiteral("Ntf")));
45  if (!mLogFile->open(QIODevice::WriteOnly | QIODevice::Truncate)) {
46  qCWarning(AKONADICORE_LOG) << "Failed to open Akonadi Session log file" << mLogFile->fileName();
47  delete mLogFile;
48  mLogFile = nullptr;
49  }
50  }
51 }
52 
53 Connection::~Connection()
54 {
55  delete mLogFile;
56  if (mSocket) {
57  mSocket->disconnect();
58  mSocket->disconnectFromServer();
59  mSocket->close();
60  mSocket.reset();
61  }
62 }
63 
64 void Connection::reconnect()
65 {
66  const bool ok = QMetaObject::invokeMethod(this, &Connection::doReconnect, Qt::QueuedConnection);
67  Q_ASSERT(ok);
68  Q_UNUSED(ok)
69 }
70 
71 QString Connection::defaultAddressForTypeAndMethod(ConnectionType type, const QString &method)
72 {
73  if (method == QLatin1String("UnixPath")) {
74  const QString defaultSocketDir = StandardDirs::saveDir("data");
75  if (type == CommandConnection) {
76  return defaultSocketDir % QStringLiteral("akonadiserver-cmd.socket");
77  } else if (type == NotificationConnection) {
78  return defaultSocketDir % QStringLiteral("akonadiserver-ntf.socket");
79  }
80  } else if (method == QLatin1String("NamedPipe")) {
81  QString suffix;
82  if (Instance::hasIdentifier()) {
83  suffix += QStringLiteral("%1-").arg(Instance::identifier());
84  }
85  suffix += QString::fromUtf8(QUrl::toPercentEncoding(qApp->applicationDirPath()));
86  if (type == CommandConnection) {
87  return QStringLiteral("Akonadi-Cmd-") % suffix;
88  } else if (type == NotificationConnection) {
89  return QStringLiteral("Akonadi-Ntf-") % suffix;
90  }
91  }
92 
93  Q_UNREACHABLE();
94 }
95 
96 void Connection::doReconnect()
97 {
98  Q_ASSERT(QThread::currentThread() == thread());
99 
100  if (mSocket && (mSocket->state() == QLocalSocket::ConnectedState || mSocket->state() == QLocalSocket::ConnectingState)) {
101  // nothing to do, we are still/already connected
102  return;
103  }
104 
105  if (ServerManager::self()->state() != ServerManager::Running) {
106  return;
107  }
108 
109  // try to figure out where to connect to
110  QString serverAddress;
111 
112  // env var has precedence
113  const QByteArray serverAddressEnvVar = qgetenv("AKONADI_SERVER_ADDRESS");
114  if (!serverAddressEnvVar.isEmpty()) {
115  const int pos = serverAddressEnvVar.indexOf(':');
116  const QByteArray protocol = serverAddressEnvVar.left(pos);
117  QMap<QString, QString> options;
118  const QStringList lst = QString::fromLatin1(serverAddressEnvVar.mid(pos + 1)).split(QLatin1Char(','));
119  for (const QString &entry : lst) {
120  const QStringList pair = entry.split(QLatin1Char('='));
121  if (pair.size() != 2) {
122  continue;
123  }
124  options.insert(pair.first(), pair.last());
125  }
126 
127  if (protocol == "unix") {
128  serverAddress = options.value(QStringLiteral("path"));
129  } else if (protocol == "pipe") {
130  serverAddress = options.value(QStringLiteral("name"));
131  }
132  }
133 
134  // try config file next, fall back to defaults if that fails as well
135  if (serverAddress.isEmpty()) {
136  const QString connectionConfigFile = StandardDirs::connectionConfigFile();
137  const QFileInfo fileInfo(connectionConfigFile);
138  if (!fileInfo.exists()) {
139  qCWarning(AKONADICORE_LOG) << "Akonadi Client Session: connection config file '"
140  "akonadi/akonadiconnectionrc' can not be found!";
141  }
142 
143  QSettings connectionSettings(connectionConfigFile, QSettings::IniFormat);
144 
145  QString connectionType;
146  if (mConnectionType == CommandConnection) {
147  connectionType = QStringLiteral("Data");
148  } else if (mConnectionType == NotificationConnection) {
149  connectionType = QStringLiteral("Notifications");
150  }
151 
152  connectionSettings.beginGroup(connectionType);
153  const auto method = connectionSettings.value(QStringLiteral("Method"), QStringLiteral("UnixPath")).toString();
154  serverAddress = connectionSettings.value(method, defaultAddressForTypeAndMethod(mConnectionType, method)).toString();
155  }
156 
157  mSocket.reset(new QLocalSocket(this));
158  connect(mSocket.data(),
160  this,
161  [this](QLocalSocket::LocalSocketError /*unused*/) {
162  qCWarning(AKONADICORE_LOG) << mSocket->errorString() << mSocket->serverName();
163  Q_EMIT socketError(mSocket->errorString());
164  Q_EMIT socketDisconnected();
165  });
166  connect(mSocket.data(), &QLocalSocket::disconnected, this, &Connection::socketDisconnected);
167  // note: we temporarily disconnect from readyRead-signal inside handleIncomingData()
168  connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData);
169 
170  // actually do connect
171  qCDebug(AKONADICORE_LOG) << "connectToServer" << serverAddress;
172  mSocket->connectToServer(serverAddress);
173  if (!mSocket->waitForConnected()) {
174  qCWarning(AKONADICORE_LOG) << "Failed to connect to server!";
175  Q_EMIT socketError(tr("Failed to connect to server!"));
176  mSocket.reset();
177  return;
178  }
179 
180  QTimer::singleShot(0, this, &Connection::handleIncomingData);
181 
182  Q_EMIT reconnected();
183 }
184 
185 void Connection::forceReconnect()
186 {
187  const bool ok = QMetaObject::invokeMethod(this, &Connection::doForceReconnect, Qt::QueuedConnection);
188 
189  Q_ASSERT(ok);
190  Q_UNUSED(ok)
191 }
192 
193 void Connection::doForceReconnect()
194 {
195  Q_ASSERT(QThread::currentThread() == thread());
196 
197  if (mSocket) {
198  disconnect(mSocket.get(), &QLocalSocket::disconnected, this, &Connection::socketDisconnected);
199  mSocket->disconnectFromServer();
200  mSocket.reset();
201  }
202 }
203 
204 void Connection::closeConnection()
205 {
206  const bool ok = QMetaObject::invokeMethod(this, &Connection::doCloseConnection, Qt::QueuedConnection);
207  Q_ASSERT(ok);
208  Q_UNUSED(ok)
209 }
210 
211 void Connection::doCloseConnection()
212 {
213  Q_ASSERT(QThread::currentThread() == thread());
214 
215  if (mSocket) {
216  mSocket->close();
217  mSocket.reset();
218  }
219 }
220 
221 QLocalSocket *Connection::socket() const
222 {
223  return mSocket.data();
224 }
225 
226 void Connection::handleIncomingData()
227 {
228  Q_ASSERT(QThread::currentThread() == thread());
229 
230  if (!mSocket) { // not connected yet
231  return;
232  }
233 
234  while (mSocket->bytesAvailable() >= int(sizeof(qint64))) {
235  Protocol::DataStream stream(mSocket.data());
236  qint64 tag;
237  stream >> tag;
238 
239  // temporarily disconnect from readyRead-signal to avoid re-entering this function when we
240  // call waitForData() deep inside Protocol::deserialize
241  disconnect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData);
242 
244  try {
245  cmd = Protocol::deserialize(mSocket.data());
246  } catch (const Akonadi::ProtocolException &e) {
247  qCWarning(AKONADICORE_LOG) << "Protocol exception:" << e.what();
248  // cmd's type will be Invalid by default, so fall-through
249  }
250 
251  // reconnect to the signal again
252  connect(mSocket.data(), &QLocalSocket::readyRead, this, &Connection::handleIncomingData);
253 
254  if (!cmd || (cmd->type() == Protocol::Command::Invalid)) {
255  qCWarning(AKONADICORE_LOG) << "Invalid command, the world is going to end!";
256  mSocket->close();
257  reconnect();
258  return;
259  }
260 
261  if (mLogFile) {
262  mLogFile->write("S: ");
263  mLogFile->write(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz ")).toUtf8());
264  mLogFile->write(QByteArray::number(tag));
265  mLogFile->write(" ");
266  mLogFile->write(Protocol::debugString(cmd).toUtf8());
267  mLogFile->write("\n\n");
268  mLogFile->flush();
269  }
270 
271  if (cmd->type() == Protocol::Command::Hello) {
272  Q_ASSERT(cmd->isResponse());
273  }
274 
275  {
276  CommandBufferLocker locker(mCommandBuffer);
277  mCommandBuffer->enqueue(tag, cmd);
278  }
279  }
280 }
281 
282 void Connection::sendCommand(qint64 tag, const Protocol::CommandPtr &cmd)
283 {
284  const bool ok = QMetaObject::invokeMethod(this, "doSendCommand", Qt::QueuedConnection, Q_ARG(qint64, tag), Q_ARG(Akonadi::Protocol::CommandPtr, cmd));
285  Q_ASSERT(ok);
286  Q_UNUSED(ok)
287 }
288 
289 void Connection::doSendCommand(qint64 tag, const Protocol::CommandPtr &cmd)
290 {
291  Q_ASSERT(QThread::currentThread() == thread());
292 
293  if (mLogFile) {
294  mLogFile->write("C: ");
295  mLogFile->write(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz ")).toUtf8());
296  mLogFile->write(QByteArray::number(tag));
297  mLogFile->write(" ");
298  mLogFile->write(Protocol::debugString(cmd).toUtf8());
299  mLogFile->write("\n\n");
300  mLogFile->flush();
301  }
302 
303  if (mSocket && mSocket->isOpen()) {
304  Protocol::DataStream stream(mSocket.data());
305  try {
306  stream << tag;
307  Protocol::serialize(stream, cmd);
308  stream.flush();
309  } catch (const Akonadi::ProtocolException &e) {
310  qCWarning(AKONADICORE_LOG) << "Protocol Exception:" << QString::fromUtf8(e.what());
311  mSocket->close();
312  reconnect();
313  return;
314  }
315  if (!mSocket->waitForBytesWritten()) {
316  qCWarning(AKONADICORE_LOG) << "Socket write timeout";
317  mSocket->close();
318  reconnect();
319  return;
320  }
321  } else {
322  // TODO: Queue the commands and resend on reconnect?
323  }
324 }
T & first()
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
int indexOf(char ch, int from) const const
const T value(const Key &key, const T &defaultValue) const const
QDateTime currentDateTime()
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
static ServerManager * self()
Returns the singleton instance of this class, for connecting to its signals.
QByteArray number(int n, int base)
@ Running
Server is running and operational.
Definition: servermanager.h:39
qint64 applicationPid()
void errorOccurred(QLocalSocket::LocalSocketError socketError)
QMap::iterator insert(const Key &key, const T &value)
int size() const const
char * toString(const T &value)
QByteArray mid(int pos, int len) const const
bool isEmpty() const const
void disconnected()
void readyRead()
QThread * currentThread()
QueuedConnection
QByteArray toPercentEncoding(const QString &input, const QByteArray &exclude, const QByteArray &include)
T & last()
QByteArray left(int len) const const
bool isEmpty() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString fromLatin1(const char *str, int size)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
Helper integration between Akonadi and Qt.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jun 30 2022 03:51:45 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.