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"]);
165 qDebug() << "...done.";
169 void Core::saveState() {
171 QList<QVariant> users;
172 foreach(CoreSession *sess, instance()->sessions.values()) {
174 m["UserId"] = sess->userId();
175 m["State"] = sess->state();
178 s.setCoreState(users);
181 CoreSession *Core::session(UserId uid) {
182 Core *core = instance();
183 if(core->sessions.contains(uid)) return core->sessions[uid];
187 CoreSession *Core::localSession() {
188 Core *core = instance();
189 if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
193 CoreSession *Core::createSession(UserId uid) {
194 Core *core = instance();
195 Q_ASSERT(!core->sessions.contains(uid));
196 CoreSession *sess = new CoreSession(uid, core->storage);
197 core->sessions[uid] = sess;
201 bool Core::startListening(uint port) {
202 if(!server.listen(QHostAddress::Any, port)) {
203 qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
206 qDebug() << "Listening for GUI clients on port" << server.serverPort();
210 void Core::stopListening() {
212 qDebug() << "No longer listening for GUI clients.";
215 void Core::incomingConnection() {
216 // TODO implement SSL
217 while (server.hasPendingConnections()) {
218 QTcpSocket *socket = server.nextPendingConnection();
219 connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
220 connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
221 blockSizes.insert(socket, (quint32)0);
222 qDebug() << "Client connected from " << socket->peerAddress().toString();
226 qDebug() << "Closing server for basic setup.";
231 void Core::clientHasData() {
232 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
233 Q_ASSERT(socket && blockSizes.contains(socket));
234 quint32 bsize = blockSizes.value(socket);
236 if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
237 // we need to auth the client
239 QVariantMap msg = item.toMap();
240 if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
241 throw Exception("GUI client version mismatch");
244 processClientInit(socket, msg);
246 processCoreSetup(socket, msg);
248 } catch(Storage::AuthError) {
249 qWarning() << "Authentification error!"; // FIXME: send auth error to client
252 } catch(Exception e) {
253 qWarning() << "Client init error:" << e.msg();
258 blockSizes[socket] = bsize = 0; // FIXME blockSizes aufr�um0rn!
261 // FIXME: no longer called, since connection handling is now in SignalProxy
262 // No, it is called as long as core is not configured. (kaffeedoktor)
263 void Core::clientDisconnected() {
264 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
265 blockSizes.remove(socket);
266 qDebug() << "Client disconnected.";
268 // make server listen again if still not configured
273 // TODO remove unneeded sessions - if necessary/possible...
276 QVariant Core::connectLocalClient(QString user, QString passwd) {
277 UserId uid = instance()->storage->validateUser(user, passwd);
278 QVariant reply = instance()->initSession(uid);
279 instance()->guiUser = uid;
280 qDebug() << "Local client connected.";
284 void Core::disconnectLocalClient() {
285 qDebug() << "Local client disconnected.";
286 instance()->guiUser = 0;
289 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
292 UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString()); // throws exception if this failed
293 reply["StartWizard"] = false;
294 reply["Reply"] = initSession(uid);
295 disconnect(socket, 0, this, 0);
296 sessions[uid]->addClient(socket);
297 qDebug() << "Client initialized successfully.";
298 SignalProxy::writeDataToDevice(socket, reply);
301 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
302 if(msg["HasSettings"].toBool()) {
304 auth["User"] = msg["User"];
305 auth["Password"] = msg["Password"];
307 msg.remove("Password");
308 qDebug() << "Initializing storage provider" << msg["Type"].toString();
310 if(!initStorage(msg, true)) {
311 // notify client to start wizard again
312 qWarning("Core is currently not configured!");
314 reply["StartWizard"] = true;
315 reply["StorageProviders"] = availableStorageProviders();
316 SignalProxy::writeDataToDevice(socket, reply);
318 // write coresettings
320 s.setDatabaseSettings(msg);
321 // write admin user to database & make the core listen again to connections
322 storage->addUser(auth["User"].toString(), auth["Password"].toString());
324 // continue the normal procedure
325 processClientInit(socket, auth);
328 // notify client to start wizard
330 reply["StartWizard"] = true;
331 reply["StorageProviders"] = availableStorageProviders();
332 SignalProxy::writeDataToDevice(socket, reply);
336 QVariant Core::initSession(UserId uid) {
337 // Find or create session for validated user
339 if(sessions.contains(uid))
340 sess = sessions[uid];
342 sess = createSession(uid);
344 reply["SessionState"] = sess->sessionState();
348 QStringList Core::availableStorageProviders() {
349 QStringList storageProviders;
350 if (SqliteStorage::isAvailable()) {
351 storageProviders.append(SqliteStorage::displayName());
354 // storageProviders.append("MySQL");
356 return storageProviders;