Fixed a bug in SqliteStorage::setup() which could cause the Wizard to lock up in...
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by The Quassel IRC Development Team             *
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) any later version.                                   *
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 "core.h"
22 #include "coresession.h"
23 #include "coresettings.h"
24 #include "sqlitestorage.h"
25 #include "util.h"
26
27 #include <QMetaObject>
28 #include <QMetaMethod>
29
30 Core *Core::instanceptr = 0;
31
32 Core *Core::instance() {
33   if(instanceptr) return instanceptr;
34   instanceptr = new Core();
35   instanceptr->init();
36   return instanceptr;
37 }
38
39 void Core::destroy() {
40   delete instanceptr;
41   instanceptr = 0;
42 }
43
44 Core::Core() {
45   storage = NULL;
46 }
47
48 void Core::init() {
49   CoreSettings s;
50   configured = false;
51
52   QVariantMap dbSettings = s.databaseSettings().toMap();
53   QString hname = dbSettings["Type"].toString().toLower();
54   hname[0] = hname[0].toUpper();
55   hname = "initStorage" + hname;
56   if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured),  Q_ARG(QVariantMap, dbSettings), Q_ARG(bool, false))) {
57     qWarning("No database backend configured.");
58   }
59   
60   if (!configured) {
61     qWarning("Core is currently not configured!");
62   }
63     
64   connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
65   startListening(s.port());
66   guiUser = 0;
67 }
68
69 bool Core::initStorageSqlite(QVariantMap dbSettings, bool setup) {
70   if (!SqliteStorage::isAvailable()) {
71     qFatal("Sqlite is currently required! Please make sure your Qt library has sqlite support enabled.");
72   }
73   if (storage) {
74     qDebug() << "Deleting old storage object.";
75     delete storage;
76     storage = NULL;
77   }
78   
79   storage = new SqliteStorage();
80   if (setup && !storage->setup(dbSettings)) {
81     return false;
82   }
83   
84   return storage->init(dbSettings);
85 }
86
87 Core::~Core() {
88   qDeleteAll(sessions);
89   if (storage) {
90     delete storage;
91     storage = NULL;
92   }
93 }
94
95 CoreSession *Core::session(UserId uid) {
96   Core *core = instance();
97   if(core->sessions.contains(uid)) return core->sessions[uid];
98   else return 0;
99 }
100
101 CoreSession *Core::localSession() {
102   Core *core = instance();
103   if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
104   else return 0;
105 }
106
107 CoreSession *Core::createSession(UserId uid) {
108   Core *core = instance();
109   Q_ASSERT(!core->sessions.contains(uid));
110   CoreSession *sess = new CoreSession(uid, core->storage);
111   core->sessions[uid] = sess;
112   return sess;
113 }
114
115 bool Core::startListening(uint port) {
116   if(!server.listen(QHostAddress::Any, port)) {
117     qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
118     return false;
119   }
120   qDebug() << "Listening for GUI clients on port" << server.serverPort();
121   return true;
122 }
123
124 void Core::stopListening() {
125   server.close();
126   qDebug() << "No longer listening for GUI clients.";
127 }
128
129 void Core::incomingConnection() {
130   // TODO implement SSL
131   while (server.hasPendingConnections()) {
132     QTcpSocket *socket = server.nextPendingConnection();
133     connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
134     connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
135     blockSizes.insert(socket, (quint32)0);
136     qDebug() << "Client connected from " << socket->peerAddress().toString();
137     
138     if (!configured) {
139       server.close();
140       qDebug() << "Closing server for basic setup.";
141     }
142   }
143 }
144
145 void Core::clientHasData() {
146   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
147   Q_ASSERT(socket && blockSizes.contains(socket));
148   quint32 bsize = blockSizes.value(socket);
149   QVariant item;
150   if(readDataFromDevice(socket, bsize, item)) {
151     // we need to auth the client
152     try {
153       QVariantMap msg = item.toMap();
154       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
155         throw Exception("GUI client version mismatch");
156       }
157       if (configured) {
158         processClientInit(socket, msg);
159       } else {
160         processCoreSetup(socket, msg);
161       }
162     } catch(Storage::AuthError) {
163       qWarning() << "Authentification error!";  // FIXME: send auth error to client
164       socket->close();
165       return;
166     } catch(Exception e) {
167       qWarning() << "Client init error:" << e.msg();
168       socket->close();
169       return;
170     }
171   }
172   blockSizes[socket] = bsize = 0;  // FIXME blockSizes aufräum0rn!
173 }
174
175 // FIXME: no longer called, since connection handling is now in SignalProxy
176 // No, it is called as long as core is not configured. (kaffeedoktor)
177 void Core::clientDisconnected() {
178   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
179   blockSizes.remove(socket);
180   qDebug() << "Client disconnected.";
181   
182   // make server listen again if still not configured
183   if (!configured) {
184     startListening();
185   }
186   
187   // TODO remove unneeded sessions - if necessary/possible...
188 }
189
190 QVariant Core::connectLocalClient(QString user, QString passwd) {
191   UserId uid = instance()->storage->validateUser(user, passwd);
192   QVariant reply = instance()->initSession(uid);
193   instance()->guiUser = uid;
194   qDebug() << "Local client connected.";
195   return reply;
196 }
197
198 void Core::disconnectLocalClient() {
199   qDebug() << "Local client disconnected.";
200   instance()->guiUser = 0;
201 }
202
203 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
204   // Auth
205   QVariantMap reply;
206   UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString()); // throws exception if this failed
207   reply["StartWizard"] = false;
208   reply["Reply"] = initSession(uid);
209   disconnect(socket, 0, this, 0);
210   sessions[uid]->addClient(socket);
211   qDebug() << "Client initialized successfully.";
212   writeDataToDevice(socket, reply);
213 }
214
215 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
216   if(msg["HasSettings"].toBool()) {
217     QVariantMap auth;
218     auth["User"] = msg["User"];
219     auth["Password"] = msg["Password"];
220     msg.remove("User");
221     msg.remove("Password");
222     qDebug() << "Initializing storage provider" << msg["Type"].toString();
223     QString hname = msg["Type"].toString().toLower();
224     hname[0] = hname[0].toUpper();
225     hname = "initStorage" + hname;
226     if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured),  Q_ARG(QVariantMap, msg), Q_ARG(bool, true))) {
227       qWarning("No database backend configured.");
228     }
229     if (!configured) {
230       // notify client to start wizard again
231       qWarning("Core is currently not configured!");
232       QVariantMap reply;
233       reply["StartWizard"] = true;
234       reply["StorageProviders"] = availableStorageProviders();
235       writeDataToDevice(socket, reply);
236     } else {
237       // write coresettings
238       CoreSettings s;
239       s.setDatabaseSettings(msg);
240       // write admin user to database & make the core listen again to connections
241       storage->addUser(auth["User"].toString(), auth["Password"].toString());
242       startListening();
243       // continue the normal procedure
244       processClientInit(socket, auth);
245     }
246   } else {
247     // notify client to start wizard
248     QVariantMap reply;
249     reply["StartWizard"] = true;
250     reply["StorageProviders"] = availableStorageProviders();
251     writeDataToDevice(socket, reply);
252   }
253 }
254
255 QVariant Core::initSession(UserId uid) {
256   // Find or create session for validated user
257   CoreSession *sess;
258   if(sessions.contains(uid))
259     sess = sessions[uid];
260   else
261     sess = createSession(uid);
262   QVariantMap reply;
263   reply["SessionState"] = sess->sessionState();
264   return reply;
265 }
266
267 QStringList Core::availableStorageProviders() {
268   QStringList storageProviders;
269   if (SqliteStorage::isAvailable()) {
270     storageProviders.append(SqliteStorage::displayName());
271   }
272   // TODO: temporary
273   storageProviders.append("MySQL");
274   
275   return storageProviders;
276 }