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 ***************************************************************************/
22 #include "coresession.h"
23 #include "coresettings.h"
24 #include "signalproxy.h"
25 #include "sqlitestorage.h"
27 #include <QMetaObject>
28 #include <QMetaMethod>
30 #include <QCoreApplication>
32 Core *Core::instanceptr = 0;
34 Core *Core::instance() {
35 if(instanceptr) return instanceptr;
36 instanceptr = new Core();
41 void Core::destroy() {
52 // TODO: Remove this again at some point
53 // Check if old core settings need to be migrated in order to make the switch to the
54 // new location less painful.
56 QVariant foo = cs.databaseSettings();
59 // ok, no settings stored yet. check for old ones.
61 QSettings os("quassel-irc.org", "Quassel IRC", this);
63 QSettings os("Quassel IRC Development Team", "Quassel IRC");
65 QVariant bar = os.value("Core/DatabaseSettings");
67 // old settings available -- migrate!
68 qWarning() << "\n\nOld settings detected. Will migrate core settings to the new location...\nNOTE: GUI style settings won't be migrated!\n";
70 QSettings ncs("quassel-irc.org", "Quassel Core");
72 QSettings ncs("Quassel Project", "Quassel Core");
74 ncs.setValue("Core/CoreState", os.value("Core/CoreState"));
75 ncs.setValue("Core/DatabaseSettings", os.value("Core/DatabaseSettings"));
76 os.beginGroup("SessionData");
77 foreach(QString group, os.childGroups()) {
78 ncs.setValue(QString("CoreUser/%1/SessionData/Identities").arg(group), os.value(QString("%1/Identities").arg(group)));
79 ncs.setValue(QString("CoreUser/%1/SessionData/Networks").arg(group), os.value(QString("%1/Networks").arg(group)));
83 QSettings ngs("quassel-irc.org", "Quassel Client");
85 QSettings ngs("Quassel Project", "Quassel Client");
87 os.beginGroup("Accounts");
88 foreach(QString key, os.childKeys()) {
89 ngs.setValue(QString("Accounts/%1").arg(key), os.value(key));
91 foreach(QString group, os.childGroups()) {
92 ngs.setValue(QString("Accounts/%1/AccountData").arg(group), os.value(QString("%1/AccountData").arg(group)));
95 os.beginGroup("Geometry");
96 foreach(QString key, os.childKeys()) {
97 ngs.setValue(QString("UI/%1").arg(key), os.value(key));
103 qWarning() << "Migration successfully finished. You may now delete $HOME/.config/Quassel IRC Development Team/ (on Linux).\n\n";
110 if(!(configured = initStorage(cs.databaseSettings().toMap()))) {
111 qWarning("Core is currently not configured!");
114 connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
115 startListening(cs.port());
120 bool Core::initStorage(QVariantMap dbSettings, bool setup) {
121 QString engine = dbSettings["Type"].toString().toLower();
124 qDebug() << "Deleting old storage object.";
125 storage->deleteLater();
129 // FIXME register new storageProviders here
130 if(engine == "sqlite" && SqliteStorage::isAvailable()) {
131 storage = new SqliteStorage(this);
133 qWarning() << "Selected StorageBackend is not available:" << dbSettings["Type"].toString();
134 return configured = false;
137 if(setup && !storage->setup(dbSettings)) {
138 return configured = false;
141 return configured = storage->init(dbSettings);
144 bool Core::initStorage(QVariantMap dbSettings) {
145 return initStorage(dbSettings, false);
149 qDeleteAll(sessions);
152 void Core::restoreState() {
153 Q_ASSERT(!instance()->sessions.count());
155 QList<QVariant> users = s.coreState().toList();
156 if(users.count() > 0) {
157 qDebug() << "Restoring previous core state...";
158 foreach(QVariant v, users) {
159 QVariantMap m = v.toMap();
160 if(m.contains("UserId")) {
161 CoreSession *sess = createSession(m["UserId"].toUInt());
162 sess->restoreState(m["State"]);
168 void Core::saveState() {
170 QList<QVariant> users;
171 foreach(CoreSession *sess, instance()->sessions.values()) {
173 m["UserId"] = sess->userId();
174 m["State"] = sess->state();
177 s.setCoreState(users);
180 CoreSession *Core::session(UserId uid) {
181 Core *core = instance();
182 if(core->sessions.contains(uid)) return core->sessions[uid];
186 CoreSession *Core::localSession() {
187 Core *core = instance();
188 if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
192 CoreSession *Core::createSession(UserId uid) {
193 Core *core = instance();
194 Q_ASSERT(!core->sessions.contains(uid));
195 CoreSession *sess = new CoreSession(uid, core->storage);
196 core->sessions[uid] = sess;
200 bool Core::startListening(uint port) {
201 if(!server.listen(QHostAddress::Any, port)) {
202 qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
205 qDebug() << "Listening for GUI clients on port" << server.serverPort();
209 void Core::stopListening() {
211 qDebug() << "No longer listening for GUI clients.";
214 void Core::incomingConnection() {
215 // TODO implement SSL
216 while (server.hasPendingConnections()) {
217 QTcpSocket *socket = server.nextPendingConnection();
218 connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
219 connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
220 blockSizes.insert(socket, (quint32)0);
221 qDebug() << "Client connected from " << socket->peerAddress().toString();
225 qDebug() << "Closing server for basic setup.";
230 void Core::clientHasData() {
231 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
232 Q_ASSERT(socket && blockSizes.contains(socket));
233 quint32 bsize = blockSizes.value(socket);
235 if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
236 // we need to auth the client
238 QVariantMap msg = item.toMap();
239 if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
240 throw Exception("GUI client version mismatch");
243 processClientInit(socket, msg);
245 processCoreSetup(socket, msg);
247 } catch(Storage::AuthError) {
248 qWarning() << "Authentification error!"; // FIXME: send auth error to client
251 } catch(Exception e) {
252 qWarning() << "Client init error:" << e.msg();
257 blockSizes[socket] = bsize = 0; // FIXME blockSizes aufräum0rn!
260 // FIXME: no longer called, since connection handling is now in SignalProxy
261 // No, it is called as long as core is not configured. (kaffeedoktor)
262 void Core::clientDisconnected() {
263 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
264 blockSizes.remove(socket);
265 qDebug() << "Client disconnected.";
267 // make server listen again if still not configured
272 // TODO remove unneeded sessions - if necessary/possible...
275 QVariant Core::connectLocalClient(QString user, QString passwd) {
276 UserId uid = instance()->storage->validateUser(user, passwd);
277 QVariant reply = instance()->initSession(uid);
278 instance()->guiUser = uid;
279 qDebug() << "Local client connected.";
283 void Core::disconnectLocalClient() {
284 qDebug() << "Local client disconnected.";
285 instance()->guiUser = 0;
288 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
291 UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString()); // throws exception if this failed
292 reply["StartWizard"] = false;
293 reply["Reply"] = initSession(uid);
294 disconnect(socket, 0, this, 0);
295 sessions[uid]->addClient(socket);
296 qDebug() << "Client initialized successfully.";
297 SignalProxy::writeDataToDevice(socket, reply);
300 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
301 if(msg["HasSettings"].toBool()) {
303 auth["User"] = msg["User"];
304 auth["Password"] = msg["Password"];
306 msg.remove("Password");
307 qDebug() << "Initializing storage provider" << msg["Type"].toString();
309 if(!initStorage(msg, true)) {
310 // notify client to start wizard again
311 qWarning("Core is currently not configured!");
313 reply["StartWizard"] = true;
314 reply["StorageProviders"] = availableStorageProviders();
315 SignalProxy::writeDataToDevice(socket, reply);
317 // write coresettings
319 s.setDatabaseSettings(msg);
320 // write admin user to database & make the core listen again to connections
321 storage->addUser(auth["User"].toString(), auth["Password"].toString());
323 // continue the normal procedure
324 processClientInit(socket, auth);
327 // notify client to start wizard
329 reply["StartWizard"] = true;
330 reply["StorageProviders"] = availableStorageProviders();
331 SignalProxy::writeDataToDevice(socket, reply);
335 QVariant Core::initSession(UserId uid) {
336 // Find or create session for validated user
338 if(sessions.contains(uid))
339 sess = sessions[uid];
341 sess = createSession(uid);
343 reply["SessionState"] = sess->sessionState();
347 QStringList Core::availableStorageProviders() {
348 QStringList storageProviders;
349 if (SqliteStorage::isAvailable()) {
350 storageProviders.append(SqliteStorage::displayName());
353 // storageProviders.append("MySQL");
355 return storageProviders;