1 /***************************************************************************
2 * Copyright (C) 2005-07 by the Quassel IRC Team *
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 Core *Core::instanceptr = 0;
32 Core *Core::instance() {
33 if(instanceptr) return instanceptr;
34 instanceptr = new Core();
39 void Core::destroy() {
50 // TODO: Remove this again at some point
51 // Check if old core settings need to be migrated in order to make the switch to the
52 // new location less painful.
54 QVariant foo = cs.databaseSettings();
56 // ok, no settings stored yet. check for old ones.
57 QSettings os("Quassel IRC Development Team", "Quassel IRC");
58 QVariant bar = os.value("Core/DatabaseSettings");
60 // old settings available -- migrate!
61 qWarning() << "\n\nOld settings detected. Will migrate core settings to the new location...\nNOTE: GUI style settings won't be migrated!\n";
62 QSettings ncs("Quassel Project", "Quassel Core");
63 ncs.setValue("Core/CoreState", os.value("Core/CoreState"));
64 ncs.setValue("Core/DatabaseSettings", os.value("Core/DatabaseSettings"));
65 os.beginGroup("SessionData");
66 foreach(QString group, os.childGroups()) {
67 ncs.setValue(QString("SessionData/%1/Identities").arg(group), os.value(QString("%1/Identities").arg(group)));
68 ncs.setValue(QString("SessionData/%1/Networks").arg(group), os.value(QString("%1/Networks").arg(group)));
72 QSettings ngs("Quassel Project", "Quassel Client");
73 os.beginGroup("Accounts");
74 foreach(QString key, os.childKeys()) {
75 ngs.setValue(QString("Accounts/%1").arg(key), os.value(key));
77 foreach(QString group, os.childGroups()) {
78 ngs.setValue(QString("Accounts/%1/AccountData").arg(group), os.value(QString("%1/AccountData").arg(group)));
81 os.beginGroup("Geometry");
82 foreach(QString key, os.childKeys()) {
83 ngs.setValue(QString("UI/%1").arg(key), os.value(key));
89 qWarning() << "Migration successfully finished. You may now delete $HOME/.config/Quassel IRC Development Team/ (on Linux).\n\n";
97 QVariantMap dbSettings = s.databaseSettings().toMap();
98 QString hname = dbSettings["Type"].toString().toLower();
99 hname[0] = hname[0].toUpper();
100 hname = "initStorage" + hname;
101 if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured), Q_ARG(QVariantMap, dbSettings), Q_ARG(bool, false))) {
102 qWarning("No database backend configured.");
106 qWarning("Core is currently not configured!");
109 connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
110 startListening(s.port());
115 bool Core::initStorageSqlite(QVariantMap dbSettings, bool setup) {
116 if (!SqliteStorage::isAvailable()) {
117 qFatal("Sqlite is currently required! Please make sure your Qt library has sqlite support enabled.");
120 qDebug() << "Deleting old storage object.";
125 storage = new SqliteStorage();
126 if (setup && !storage->setup(dbSettings)) {
130 return storage->init(dbSettings);
134 qDeleteAll(sessions);
141 void Core::restoreState() {
142 Q_ASSERT(!instance()->sessions.count());
144 QList<QVariant> users = s.coreState().toList();
145 if(users.count() > 0) {
146 qDebug() << "Restoring previous core state...";
147 foreach(QVariant v, users) {
148 QVariantMap m = v.toMap();
149 if(m.contains("UserId")) {
150 CoreSession *sess = createSession(m["UserId"].toUInt());
151 sess->restoreState(m["State"]);
157 void Core::saveState() {
159 QList<QVariant> users;
160 foreach(CoreSession *sess, instance()->sessions.values()) {
162 m["UserId"] = sess->userId();
163 m["State"] = sess->state();
166 s.setCoreState(users);
169 CoreSession *Core::session(UserId uid) {
170 Core *core = instance();
171 if(core->sessions.contains(uid)) return core->sessions[uid];
175 CoreSession *Core::localSession() {
176 Core *core = instance();
177 if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
181 CoreSession *Core::createSession(UserId uid) {
182 Core *core = instance();
183 Q_ASSERT(!core->sessions.contains(uid));
184 CoreSession *sess = new CoreSession(uid, core->storage);
185 core->sessions[uid] = sess;
189 bool Core::startListening(uint port) {
190 if(!server.listen(QHostAddress::Any, port)) {
191 qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
194 qDebug() << "Listening for GUI clients on port" << server.serverPort();
198 void Core::stopListening() {
200 qDebug() << "No longer listening for GUI clients.";
203 void Core::incomingConnection() {
204 // TODO implement SSL
205 while (server.hasPendingConnections()) {
206 QTcpSocket *socket = server.nextPendingConnection();
207 connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
208 connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
209 blockSizes.insert(socket, (quint32)0);
210 qDebug() << "Client connected from " << socket->peerAddress().toString();
214 qDebug() << "Closing server for basic setup.";
219 void Core::clientHasData() {
220 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
221 Q_ASSERT(socket && blockSizes.contains(socket));
222 quint32 bsize = blockSizes.value(socket);
224 if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
225 // we need to auth the client
227 QVariantMap msg = item.toMap();
228 if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
229 throw Exception("GUI client version mismatch");
232 processClientInit(socket, msg);
234 processCoreSetup(socket, msg);
236 } catch(Storage::AuthError) {
237 qWarning() << "Authentification error!"; // FIXME: send auth error to client
240 } catch(Exception e) {
241 qWarning() << "Client init error:" << e.msg();
246 blockSizes[socket] = bsize = 0; // FIXME blockSizes aufr�um0rn!
249 // FIXME: no longer called, since connection handling is now in SignalProxy
250 // No, it is called as long as core is not configured. (kaffeedoktor)
251 void Core::clientDisconnected() {
252 QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
253 blockSizes.remove(socket);
254 qDebug() << "Client disconnected.";
256 // make server listen again if still not configured
261 // TODO remove unneeded sessions - if necessary/possible...
264 QVariant Core::connectLocalClient(QString user, QString passwd) {
265 UserId uid = instance()->storage->validateUser(user, passwd);
266 QVariant reply = instance()->initSession(uid);
267 instance()->guiUser = uid;
268 qDebug() << "Local client connected.";
272 void Core::disconnectLocalClient() {
273 qDebug() << "Local client disconnected.";
274 instance()->guiUser = 0;
277 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
280 UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString()); // throws exception if this failed
281 reply["StartWizard"] = false;
282 reply["Reply"] = initSession(uid);
283 disconnect(socket, 0, this, 0);
284 sessions[uid]->addClient(socket);
285 qDebug() << "Client initialized successfully.";
286 SignalProxy::writeDataToDevice(socket, reply);
289 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
290 if(msg["HasSettings"].toBool()) {
292 auth["User"] = msg["User"];
293 auth["Password"] = msg["Password"];
295 msg.remove("Password");
296 qDebug() << "Initializing storage provider" << msg["Type"].toString();
297 QString hname = msg["Type"].toString().toLower();
298 hname[0] = hname[0].toUpper();
299 hname = "initStorage" + hname;
300 if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured), Q_ARG(QVariantMap, msg), Q_ARG(bool, true))) {
301 qWarning("No database backend configured.");
304 // notify client to start wizard again
305 qWarning("Core is currently not configured!");
307 reply["StartWizard"] = true;
308 reply["StorageProviders"] = availableStorageProviders();
309 SignalProxy::writeDataToDevice(socket, reply);
311 // write coresettings
313 s.setDatabaseSettings(msg);
314 // write admin user to database & make the core listen again to connections
315 storage->addUser(auth["User"].toString(), auth["Password"].toString());
317 // continue the normal procedure
318 processClientInit(socket, auth);
321 // notify client to start wizard
323 reply["StartWizard"] = true;
324 reply["StorageProviders"] = availableStorageProviders();
325 SignalProxy::writeDataToDevice(socket, reply);
329 QVariant Core::initSession(UserId uid) {
330 // Find or create session for validated user
332 if(sessions.contains(uid))
333 sess = sessions[uid];
335 sess = createSession(uid);
337 reply["SessionState"] = sess->sessionState();
341 QStringList Core::availableStorageProviders() {
342 QStringList storageProviders;
343 if (SqliteStorage::isAvailable()) {
344 storageProviders.append(SqliteStorage::displayName());
347 storageProviders.append("MySQL");
349 return storageProviders;