1 /***************************************************************************
2 * Copyright (C) 2005-08 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include <QMetaObject>
22 #include <QMetaMethod>
23 #include <QMutexLocker>
24 #include <QCoreApplication>
27 #include "coresession.h"
28 #include "coresettings.h"
29 #include "signalproxy.h"
30 #include "sqlitestorage.h"
33 Core *Core::instanceptr = 0;
36 Core *Core::instance() {
37 if(instanceptr) return instanceptr;
38 instanceptr = new Core();
43 void Core::destroy() {
51 startTime = QDateTime::currentDateTime(); // for uptime :)
58 if(!(configured = initStorage(cs.databaseSettings().toMap()))) {
59 qWarning("Core is currently not configured!");
62 connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
63 startListening(cs.port());
68 bool Core::initStorage(QVariantMap dbSettings, bool setup) {
69 QString engine = dbSettings["Type"].toString().toLower();
72 qDebug() << "Deleting old storage object.";
73 storage->deleteLater();
77 // FIXME register new storageProviders here
78 if(engine == "sqlite" && SqliteStorage::isAvailable()) {
79 storage = new SqliteStorage(this);
80 connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
82 qWarning() << "Selected StorageBackend is not available:" << dbSettings["Type"].toString();
83 return configured = false;
86 if(setup && !storage->setup(dbSettings)) {
87 return configured = false;
90 return configured = storage->init(dbSettings);
94 // FIXME properly shutdown the sessions
98 void Core::restoreState() {
99 if(instance()->sessions.count()) {
100 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
104 uint build = s.coreState().toMap()["CoreBuild"].toUInt();
106 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
109 QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
110 if(activeSessions.count() > 0) {
111 qDebug() << "Restoring previous core state...";
112 foreach(QVariant v, activeSessions) {
113 UserId user = v.value<UserId>();
114 instance()->createSession(user, true);
116 qDebug() << "...done.";
120 void Core::saveState() {
123 QVariantList activeSessions;
124 foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
125 state["CoreBuild"] = Global::quasselBuild;
126 state["ActiveSessions"] = activeSessions;
127 s.setCoreState(state);
130 /*** Storage Access ***/
131 bool Core::createNetworkId(UserId user, NetworkInfo &info) {
132 QMutexLocker locker(&mutex);
133 NetworkId networkId = instance()->storage->createNetworkId(user, info);
134 if(!networkId.isValid())
137 info.networkId = networkId;
141 NetworkId Core::networkId(UserId user, const QString &network) {
142 QMutexLocker locker(&mutex);
143 return instance()->storage->getNetworkId(user, network);
146 BufferInfo Core::bufferInfo(UserId user, const NetworkId &networkId, const QString &buffer) {
147 //QMutexLocker locker(&mutex);
148 return instance()->storage->getBufferInfo(user, networkId, buffer);
151 MsgId Core::storeMessage(const Message &message) {
152 QMutexLocker locker(&mutex);
153 return instance()->storage->logMessage(message);
156 QList<Message> Core::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
157 QMutexLocker locker(&mutex);
158 return instance()->storage->requestMsgs(buffer, lastmsgs, offset);
161 QList<Message> Core::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
162 QMutexLocker locker(&mutex);
163 return instance()->storage->requestMsgs(buffer, since, offset);
166 QList<Message> Core::requestMsgRange(BufferInfo buffer, int first, int last) {
167 QMutexLocker locker(&mutex);
168 return instance()->storage->requestMsgRange(buffer, first, last);
171 QList<BufferInfo> Core::requestBuffers(UserId user, QDateTime since) {
172 QMutexLocker locker(&mutex);
173 return instance()->storage->requestBuffers(user, since);
176 /*** Network Management ***/
178 bool Core::startListening(uint port) {
179 if(!server.listen(QHostAddress::Any, port)) {
180 qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
183 qDebug() << "Listening for GUI clients on port" << server.serverPort();
187 void Core::stopListening() {
189 qDebug() << "No longer listening for GUI clients.";
192 void Core::incomingConnection() {
193 // TODO implement SSL
194 while (server.hasPendingConnections()) {
195 QTcpSocket *socket = server.nextPendingConnection();
196 connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
197 connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
198 QVariantMap clientInfo;
199 blocksizes.insert(socket, (quint32)0);
200 qDebug() << "Client connected from" << qPrintable(socket->peerAddress().toString());
204 qDebug() << "Closing server for basic setup.";
209 void Core::clientHasData() {
210 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
211 Q_ASSERT(socket && blocksizes.contains(socket));
213 while(SignalProxy::readDataFromDevice(socket, blocksizes[socket], item)) {
214 QVariantMap msg = item.toMap();
215 if(!msg.contains("MsgType")) {
216 // Client is way too old, does not even use the current init format
217 qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
221 // OK, so we have at least an init message format we can understand
222 if(msg["MsgType"] == "ClientInit") {
224 reply["CoreVersion"] = Global::quasselVersion;
225 reply["CoreDate"] = Global::quasselDate;
226 reply["CoreBuild"] = Global::quasselBuild;
227 // TODO: Make the core info configurable
228 int uptime = startTime.secsTo(QDateTime::currentDateTime());
229 int updays = uptime / 86400; uptime %= 86400;
230 int uphours = uptime / 3600; uptime %= 3600;
231 int upmins = uptime / 60;
232 reply["CoreInfo"] = tr("<b>Quassel Core Version %1 (Build >= %2)</b><br>"
233 "Up %3d%4h%5m (since %6)").arg(Global::quasselVersion).arg(Global::quasselBuild)
234 .arg(updays).arg(uphours,2,10,QChar('0')).arg(upmins,2,10,QChar('0')).arg(startTime.toString(Qt::TextDate));
236 reply["SupportSsl"] = false;
237 reply["LoginEnabled"] = true;
238 // TODO: check if we are configured, start wizard otherwise
240 // Just version information -- check it!
241 if(msg["ClientBuild"].toUInt() < Global::clientBuildNeeded) {
242 reply["MsgType"] = "ClientInitReject";
243 reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
244 "This core needs at least client version %1 (Build >= %2).<br>"
245 "Please consider upgrading your client.").arg(Global::quasselVersion).arg(Global::quasselBuild);
246 SignalProxy::writeDataToDevice(socket, reply);
247 qWarning() << qPrintable(tr("Client %1 too old, rejecting.").arg(socket->peerAddress().toString()));
248 socket->close(); return;
250 clientInfo[socket] = msg; // store for future reference
251 reply["MsgType"] = "ClientInitAck";
252 SignalProxy::writeDataToDevice(socket, reply);
253 } else if(msg["MsgType"] == "ClientLogin") {
255 if(!clientInfo.contains(socket)) {
256 reply["MsgType"] = "ClientLoginReject";
257 reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
258 SignalProxy::writeDataToDevice(socket, reply);
259 qWarning() << qPrintable(tr("Client %1 did not send an init message before trying to login, rejecting.").arg(socket->peerAddress().toString()));
260 socket->close(); return;
263 UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString());
266 reply["MsgType"] = "ClientLoginReject";
267 reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
268 SignalProxy::writeDataToDevice(socket, reply);
271 reply["MsgType"] = "ClientLoginAck";
272 SignalProxy::writeDataToDevice(socket, reply);
273 qDebug() << qPrintable(tr("Client %1 initialized and authentificated successfully as \"%2\".").arg(socket->peerAddress().toString(), msg["User"].toString()));
274 setupClientSession(socket, uid);
276 //socket->close(); return;
278 // we need to auth the client
280 QVariantMap msg = item.toMap();
281 if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
282 throw Exception("GUI client version mismatch");
285 processClientInit(socket, msg);
287 processCoreSetup(socket, msg);
289 } catch(Storage::AuthError) {
290 qWarning() << "Authentification error!"; // FIXME: send auth error to client
293 } catch(Exception e) {
294 qWarning() << "Client init error:" << e.msg();
301 // Potentially called during the initialization phase (before handing the connection off to the session)
302 void Core::clientDisconnected() {
303 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
304 blocksizes.remove(socket);
305 clientInfo.remove(socket);
306 qDebug() << qPrintable(tr("Client %1 disconnected.").arg(socket->peerAddress().toString()));
307 socket->deleteLater();
310 // make server listen again if still not configured FIXME
315 // TODO remove unneeded sessions - if necessary/possible...
316 // Suggestion: kill sessions if they are not connected to any network and client.
319 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
320 if(msg["HasSettings"].toBool()) {
322 auth["User"] = msg["User"];
323 auth["Password"] = msg["Password"];
325 msg.remove("Password");
326 qDebug() << "Initializing storage provider" << msg["Type"].toString();
328 if(!initStorage(msg, true)) {
329 // notify client to start wizard again
330 qWarning("Core is currently not configured!");
332 reply["StartWizard"] = true;
333 reply["StorageProviders"] = availableStorageProviders();
334 SignalProxy::writeDataToDevice(socket, reply);
336 // write coresettings
338 s.setDatabaseSettings(msg);
339 // write admin user to database & make the core listen again to connections
340 storage->addUser(auth["User"].toString(), auth["Password"].toString());
342 // continue the normal procedure
343 //processClientInit(socket, auth);
346 // notify client to start wizard
348 reply["StartWizard"] = true;
349 reply["StorageProviders"] = availableStorageProviders();
350 SignalProxy::writeDataToDevice(socket, reply);
354 void Core::setupClientSession(QTcpSocket *socket, UserId uid) {
355 // Find or create session for validated user
357 if(sessions.contains(uid)) sess = sessions[uid];
358 else sess = createSession(uid);
359 // Hand over socket, session then sends state itself
360 disconnect(socket, 0, this, 0);
362 qWarning() << qPrintable(tr("Could not initialize session for client %1!").arg(socket->peerAddress().toString()));
365 sess->addClient(socket);
368 SessionThread *Core::createSession(UserId uid, bool restore) {
369 if(sessions.contains(uid)) {
370 qWarning() << "Calling createSession() when a session for the user already exists!";
373 SessionThread *sess = new SessionThread(uid, restore, this);
374 sessions[uid] = sess;
379 QStringList Core::availableStorageProviders() {
380 QStringList storageProviders;
381 if (SqliteStorage::isAvailable()) {
382 storageProviders.append(SqliteStorage::displayName());
385 // storageProviders.append("MySQL");
387 return storageProviders;