Bringing back session save/restore. Old state won't be imported, unfortunately,
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include <QMetaObject>
22 #include <QMetaMethod>
23 #include <QMutexLocker>
24 #include <QCoreApplication>
25
26 #include "core.h"
27 #include "coresession.h"
28 #include "coresettings.h"
29 #include "signalproxy.h"
30 #include "sqlitestorage.h"
31
32 Core *Core::instanceptr = 0;
33 QMutex Core::mutex;
34
35 Core *Core::instance() {
36   if(instanceptr) return instanceptr;
37   instanceptr = new Core();
38   instanceptr->init();
39   return instanceptr;
40 }
41
42 void Core::destroy() {
43   delete instanceptr;
44   instanceptr = 0;
45 }
46
47 Core::Core()
48   : storage(0)
49 {
50   startTime = QDateTime::currentDateTime();  // for uptime :)
51 }
52
53 void Core::init() {
54   configured = false;
55
56   CoreSettings cs;
57   if(!(configured = initStorage(cs.databaseSettings().toMap()))) {
58     qWarning("Core is currently not configured!");
59   }
60
61   connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
62   startListening(cs.port());
63   guiUser = 0;
64
65 }
66
67 bool Core::initStorage(QVariantMap dbSettings, bool setup) {
68   QString engine = dbSettings["Type"].toString().toLower();
69
70   if(storage) {
71     qDebug() << "Deleting old storage object.";
72     storage->deleteLater();
73     storage = 0;
74   }
75
76   // FIXME register new storageProviders here
77   if(engine == "sqlite" && SqliteStorage::isAvailable()) {
78     storage = new SqliteStorage(this);
79     connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
80   } else {
81     qWarning() << "Selected StorageBackend is not available:" << dbSettings["Type"].toString();
82     return configured = false;
83   }
84
85   if(setup && !storage->setup(dbSettings)) {
86     return configured = false;
87   }
88
89   return configured = storage->init(dbSettings);
90 }
91
92 Core::~Core() {
93   // FIXME properly shutdown the sessions
94   qDeleteAll(sessions);
95 }
96
97 void Core::restoreState() {
98   if(instance()->sessions.count()) {
99     qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
100     return;
101   }
102   CoreSettings s;
103   uint build = s.coreState().toMap()["CoreBuild"].toUInt();
104   if(build < 362) {
105     qWarning() << qPrintable(tr("Core state too old, ignoring..."));
106     return;
107   }
108   QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
109   if(activeSessions.count() > 0) {
110     qDebug() << "Restoring previous core state...";
111     foreach(QVariant v, activeSessions) {
112       UserId user = v.value<UserId>();
113       instance()->createSession(user, true);
114     }
115     qDebug() << "...done.";
116   }
117 }
118
119 void Core::saveState() {
120   CoreSettings s;
121   QVariantMap state;
122   QVariantList activeSessions;
123   foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
124   state["CoreBuild"] = Global::quasselBuild;
125   state["ActiveSessions"] = activeSessions;
126   s.setCoreState(state);
127 }
128
129 /*** Storage Access ***/
130
131 NetworkId Core::networkId(UserId user, const QString &network) {
132   QMutexLocker locker(&mutex);
133   return instance()->storage->getNetworkId(user, network);
134 }
135
136 BufferInfo Core::bufferInfo(UserId user, const QString &network, const QString &buffer) {
137   //QMutexLocker locker(&mutex);
138   return instance()->storage->getBufferInfo(user, network, buffer);
139 }
140
141 MsgId Core::storeMessage(const Message &message) {
142   QMutexLocker locker(&mutex);
143   return instance()->storage->logMessage(message);
144 }
145
146 QList<Message> Core::requestMsgs(BufferInfo buffer, int lastmsgs, int offset) {
147   QMutexLocker locker(&mutex);
148   return instance()->storage->requestMsgs(buffer, lastmsgs, offset);
149 }
150
151 QList<Message> Core::requestMsgs(BufferInfo buffer, QDateTime since, int offset) {
152   QMutexLocker locker(&mutex);
153   return instance()->storage->requestMsgs(buffer, since, offset);
154 }
155
156 QList<Message> Core::requestMsgRange(BufferInfo buffer, int first, int last) {
157   QMutexLocker locker(&mutex);
158   return instance()->storage->requestMsgRange(buffer, first, last);
159 }
160
161 QList<BufferInfo> Core::requestBuffers(UserId user, QDateTime since) {
162   QMutexLocker locker(&mutex);
163   return instance()->storage->requestBuffers(user, since);
164 }
165
166 /*** Network Management ***/
167
168 bool Core::startListening(uint port) {
169   if(!server.listen(QHostAddress::Any, port)) {
170     qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
171     return false;
172   }
173   qDebug() << "Listening for GUI clients on port" << server.serverPort();
174   return true;
175 }
176
177 void Core::stopListening() {
178   server.close();
179   qDebug() << "No longer listening for GUI clients.";
180 }
181
182 void Core::incomingConnection() {
183   // TODO implement SSL
184   while (server.hasPendingConnections()) {
185     QTcpSocket *socket = server.nextPendingConnection();
186     connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
187     connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
188     QVariantMap clientInfo;
189     blocksizes.insert(socket, (quint32)0);
190     qDebug() << "Client connected from"  << qPrintable(socket->peerAddress().toString());
191
192     if (!configured) {
193       server.close();
194       qDebug() << "Closing server for basic setup.";
195     }
196   }
197 }
198
199 void Core::clientHasData() {
200   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
201   Q_ASSERT(socket && blocksizes.contains(socket));
202   QVariant item;
203   while(SignalProxy::readDataFromDevice(socket, blocksizes[socket], item)) {
204     QVariantMap msg = item.toMap();
205     if(!msg.contains("MsgType")) {
206       // Client is way too old, does not even use the current init format
207       qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
208       socket->close();
209       return;
210     }
211     // OK, so we have at least an init message format we can understand
212     if(msg["MsgType"] == "ClientInit") {
213       QVariantMap reply;
214       reply["CoreVersion"] = Global::quasselVersion;
215       reply["CoreDate"] = Global::quasselDate;
216       reply["CoreBuild"] = Global::quasselBuild;
217       // TODO: Make the core info configurable
218       int uptime = startTime.secsTo(QDateTime::currentDateTime());
219       int updays = uptime / 86400; uptime %= 86400;
220       int uphours = uptime / 3600; uptime %= 3600;
221       int upmins = uptime / 60;
222       reply["CoreInfo"] = tr("<b>Quassel Core Version %1 (Build >= %2)</b><br>"
223                              "Up %3d%4h%5m (since %6)").arg(Global::quasselVersion).arg(Global::quasselBuild)
224                              .arg(updays).arg(uphours,2,10,QChar('0')).arg(upmins,2,10,QChar('0')).arg(startTime.toString(Qt::TextDate));
225
226       reply["SupportSsl"] = false;
227       reply["LoginEnabled"] = true;
228       // TODO: check if we are configured, start wizard otherwise
229
230       // Just version information -- check it!
231       if(msg["ClientBuild"].toUInt() < Global::clientBuildNeeded) {
232         reply["MsgType"] = "ClientInitReject";
233         reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
234                             "This core needs at least client version %1 (Build >= %2).<br>"
235                             "Please consider upgrading your client.").arg(Global::quasselVersion).arg(Global::quasselBuild);
236         SignalProxy::writeDataToDevice(socket, reply);
237         qWarning() << qPrintable(tr("Client %1 too old, rejecting.").arg(socket->peerAddress().toString()));
238         socket->close(); return;
239       }
240       clientInfo[socket] = msg; // store for future reference
241       reply["MsgType"] = "ClientInitAck";
242       SignalProxy::writeDataToDevice(socket, reply);
243     } else if(msg["MsgType"] == "ClientLogin") {
244       QVariantMap reply;
245       if(!clientInfo.contains(socket)) {
246         reply["MsgType"] = "ClientLoginReject";
247         reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
248         SignalProxy::writeDataToDevice(socket, reply);
249         qWarning() << qPrintable(tr("Client %1 did not send an init message before trying to login, rejecting.").arg(socket->peerAddress().toString()));
250         socket->close(); return;
251       }
252       mutex.lock();
253       UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString());
254       mutex.unlock();
255       if(uid == 0) {
256         reply["MsgType"] = "ClientLoginReject";
257         reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
258         SignalProxy::writeDataToDevice(socket, reply);
259         continue;
260       }
261       reply["MsgType"] = "ClientLoginAck";
262       SignalProxy::writeDataToDevice(socket, reply);
263       qDebug() << qPrintable(tr("Client %1 initialized and authentificated successfully as \"%2\".").arg(socket->peerAddress().toString(), msg["User"].toString()));
264       setupClientSession(socket, uid);
265     }
266     //socket->close(); return;
267     /*
268     // we need to auth the client
269     try {
270       QVariantMap msg = item.toMap();
271       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
272         throw Exception("GUI client version mismatch");
273       }
274       if (configured) {
275         processClientInit(socket, msg);
276       } else {
277         processCoreSetup(socket, msg);
278       }
279     } catch(Storage::AuthError) {
280       qWarning() << "Authentification error!";  // FIXME: send auth error to client
281       socket->close();
282       return;
283     } catch(Exception e) {
284       qWarning() << "Client init error:" << e.msg();
285       socket->close();
286       return;
287     } */
288   }
289 }
290
291 // Potentially called during the initialization phase (before handing the connection off to the session)
292 void Core::clientDisconnected() {
293   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
294   blocksizes.remove(socket);
295   clientInfo.remove(socket);
296   qDebug() << qPrintable(tr("Client %1 disconnected.").arg(socket->peerAddress().toString()));
297   socket->deleteLater();
298   socket = 0;
299
300   // make server listen again if still not configured  FIXME
301   if (!configured) {
302     startListening();
303   }
304
305   // TODO remove unneeded sessions - if necessary/possible...
306   // Suggestion: kill sessions if they are not connected to any network and client.
307 }
308
309 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
310   if(msg["HasSettings"].toBool()) {
311     QVariantMap auth;
312     auth["User"] = msg["User"];
313     auth["Password"] = msg["Password"];
314     msg.remove("User");
315     msg.remove("Password");
316     qDebug() << "Initializing storage provider" << msg["Type"].toString();
317
318     if(!initStorage(msg, true)) {
319       // notify client to start wizard again
320       qWarning("Core is currently not configured!");
321       QVariantMap reply;
322       reply["StartWizard"] = true;
323       reply["StorageProviders"] = availableStorageProviders();
324       SignalProxy::writeDataToDevice(socket, reply);
325     } else {
326       // write coresettings
327       CoreSettings s;
328       s.setDatabaseSettings(msg);
329       // write admin user to database & make the core listen again to connections
330       storage->addUser(auth["User"].toString(), auth["Password"].toString());
331       startListening();
332       // continue the normal procedure
333       //processClientInit(socket, auth);
334     }
335   } else {
336     // notify client to start wizard
337     QVariantMap reply;
338     reply["StartWizard"] = true;
339     reply["StorageProviders"] = availableStorageProviders();
340     SignalProxy::writeDataToDevice(socket, reply);
341   }
342 }
343
344 void Core::setupClientSession(QTcpSocket *socket, UserId uid) {
345   // Find or create session for validated user
346   SessionThread *sess;
347   if(sessions.contains(uid)) sess = sessions[uid];
348   else sess = createSession(uid);
349   // Hand over socket, session then sends state itself
350   disconnect(socket, 0, this, 0);
351   if(!sess) {
352     qWarning() << qPrintable(tr("Could not initialize session for client %1!").arg(socket->peerAddress().toString()));
353     socket->close();
354   }
355   sess->addClient(socket);
356 }
357
358 SessionThread *Core::createSession(UserId uid, bool restore) {
359   if(sessions.contains(uid)) {
360     qWarning() << "Calling createSession() when a session for the user already exists!";
361     return 0;
362   }
363   SessionThread *sess = new SessionThread(uid, restore, this);
364   sessions[uid] = sess;
365   sess->start();
366   return sess;
367 }
368
369 QStringList Core::availableStorageProviders() {
370   QStringList storageProviders;
371   if (SqliteStorage::isAvailable()) {
372     storageProviders.append(SqliteStorage::displayName());
373   }
374   // TODO: temporary
375   // storageProviders.append("MySQL");
376   
377   return storageProviders;
378 }