Finally! The new identities plus a nice shiny settingspage for editing them are done!
[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 "core.h"
22 #include "coresession.h"
23 #include "coresettings.h"
24 #include "signalproxy.h"
25 #include "sqlitestorage.h"
26
27 #include <QMetaObject>
28 #include <QMetaMethod>
29
30 #include <QCoreApplication>
31
32 Core *Core::instanceptr = 0;
33
34 Core *Core::instance() {
35   if(instanceptr) return instanceptr;
36   instanceptr = new Core();
37   instanceptr->init();
38   return instanceptr;
39 }
40
41 void Core::destroy() {
42   delete instanceptr;
43   instanceptr = 0;
44 }
45
46 Core::Core()
47   : storage(0)
48 {
49 }
50
51 void Core::init() {
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.
55   CoreSettings cs;
56   QVariant foo = cs.databaseSettings();
57   
58   if(!foo.isValid()) {
59     // ok, no settings stored yet. check for old ones.
60 #ifdef Q_WS_MAC
61     QSettings os("quassel-irc.org", "Quassel IRC", this);
62 #else
63     QSettings os("Quassel IRC Development Team", "Quassel IRC");
64 #endif
65     QVariant bar = os.value("Core/DatabaseSettings");
66     if(bar.isValid()) {
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";
69 #ifdef Q_WS_MAC
70       QSettings ncs("quassel-irc.org", "Quassel Core");
71 #else
72       QSettings ncs("Quassel Project", "Quassel Core");
73 #endif
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)));
80       }
81       os.endGroup();
82 #ifdef Q_WS_MAC
83       QSettings ngs("quassel-irc.org", "Quassel Client");
84 #else
85       QSettings ngs("Quassel Project", "Quassel Client");
86 #endif
87       os.beginGroup("Accounts");
88       foreach(QString key, os.childKeys()) {
89         ngs.setValue(QString("Accounts/%1").arg(key), os.value(key));
90       }
91       foreach(QString group, os.childGroups()) {
92         ngs.setValue(QString("Accounts/%1/AccountData").arg(group), os.value(QString("%1/AccountData").arg(group)));
93       }
94       os.endGroup();
95       os.beginGroup("Geometry");
96       foreach(QString key, os.childKeys()) {
97         ngs.setValue(QString("UI/%1").arg(key), os.value(key));
98       }
99       os.endGroup();
100
101       ncs.sync();
102       ngs.sync();
103       qWarning() << "Migration successfully finished. You may now delete $HOME/.config/Quassel IRC Development Team/ (on Linux).\n\n";
104     }
105   }
106   // END
107
108   configured = false;
109
110   if(!(configured = initStorage(cs.databaseSettings().toMap()))) {
111     qWarning("Core is currently not configured!");
112   }
113   
114   connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
115   startListening(cs.port());
116   guiUser = 0;
117
118 }
119
120 bool Core::initStorage(QVariantMap dbSettings, bool setup) {
121   QString engine = dbSettings["Type"].toString().toLower();
122
123   if(storage) {
124     qDebug() << "Deleting old storage object.";
125     storage->deleteLater();
126     storage = 0;
127   }
128
129   // FIXME register new storageProviders here
130   if(engine == "sqlite" && SqliteStorage::isAvailable()) {
131     storage = new SqliteStorage(this);
132   } else {
133     qWarning() << "Selected StorageBackend is not available:" << dbSettings["Type"].toString();
134     return configured = false;
135   }
136
137   if(setup && !storage->setup(dbSettings)) {
138     return configured = false;
139   }
140
141   return configured = storage->init(dbSettings);
142 }
143
144 bool Core::initStorage(QVariantMap dbSettings) {
145   return initStorage(dbSettings, false);
146 }
147
148 Core::~Core() {
149   qDeleteAll(sessions);
150 }
151
152 void Core::restoreState() {
153   Q_ASSERT(!instance()->sessions.count());
154   CoreSettings s;
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"]);
163       }
164     }
165     qDebug() << "...done.";
166   }
167 }
168
169 void Core::saveState() {
170   CoreSettings s;
171   QList<QVariant> users;
172   foreach(CoreSession *sess, instance()->sessions.values()) {
173     QVariantMap m;
174     m["UserId"] = sess->userId();
175     m["State"] = sess->state();
176     users << m;
177   }
178   s.setCoreState(users);
179 }
180
181 CoreSession *Core::session(UserId uid) {
182   Core *core = instance();
183   if(core->sessions.contains(uid)) return core->sessions[uid];
184   else return 0;
185 }
186
187 CoreSession *Core::localSession() {
188   Core *core = instance();
189   if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
190   else return 0;
191 }
192
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;
198   return sess;
199 }
200
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());
204     return false;
205   }
206   qDebug() << "Listening for GUI clients on port" << server.serverPort();
207   return true;
208 }
209
210 void Core::stopListening() {
211   server.close();
212   qDebug() << "No longer listening for GUI clients.";
213 }
214
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();
223     
224     if (!configured) {
225       server.close();
226       qDebug() << "Closing server for basic setup.";
227     }
228   }
229 }
230
231 void Core::clientHasData() {
232   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
233   Q_ASSERT(socket && blockSizes.contains(socket));
234   quint32 bsize = blockSizes.value(socket);
235   QVariant item;
236   if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
237     // we need to auth the client
238     try {
239       QVariantMap msg = item.toMap();
240       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
241         throw Exception("GUI client version mismatch");
242       }
243       if (configured) {
244         processClientInit(socket, msg);
245       } else {
246         processCoreSetup(socket, msg);
247       }
248     } catch(Storage::AuthError) {
249       qWarning() << "Authentification error!";  // FIXME: send auth error to client
250       socket->close();
251       return;
252     } catch(Exception e) {
253       qWarning() << "Client init error:" << e.msg();
254       socket->close();
255       return;
256     }
257   }
258   blockSizes[socket] = bsize = 0;  // FIXME blockSizes aufr�um0rn!
259 }
260
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.";
267   
268   // make server listen again if still not configured
269   if (!configured) {
270     startListening();
271   }
272   
273   // TODO remove unneeded sessions - if necessary/possible...
274 }
275
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.";
281   return reply;
282 }
283
284 void Core::disconnectLocalClient() {
285   qDebug() << "Local client disconnected.";
286   instance()->guiUser = 0;
287 }
288
289 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
290   // Auth
291   QVariantMap reply;
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);
299 }
300
301 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
302   if(msg["HasSettings"].toBool()) {
303     QVariantMap auth;
304     auth["User"] = msg["User"];
305     auth["Password"] = msg["Password"];
306     msg.remove("User");
307     msg.remove("Password");
308     qDebug() << "Initializing storage provider" << msg["Type"].toString();
309
310     if(!initStorage(msg, true)) {
311       // notify client to start wizard again
312       qWarning("Core is currently not configured!");
313       QVariantMap reply;
314       reply["StartWizard"] = true;
315       reply["StorageProviders"] = availableStorageProviders();
316       SignalProxy::writeDataToDevice(socket, reply);
317     } else {
318       // write coresettings
319       CoreSettings s;
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());
323       startListening();
324       // continue the normal procedure
325       processClientInit(socket, auth);
326     }
327   } else {
328     // notify client to start wizard
329     QVariantMap reply;
330     reply["StartWizard"] = true;
331     reply["StorageProviders"] = availableStorageProviders();
332     SignalProxy::writeDataToDevice(socket, reply);
333   }
334 }
335
336 QVariant Core::initSession(UserId uid) {
337   // Find or create session for validated user
338   CoreSession *sess;
339   if(sessions.contains(uid))
340     sess = sessions[uid];
341   else
342     sess = createSession(uid);
343   QVariantMap reply;
344   reply["SessionState"] = sess->sessionState();
345   return reply;
346 }
347
348 QStringList Core::availableStorageProviders() {
349   QStringList storageProviders;
350   if (SqliteStorage::isAvailable()) {
351     storageProviders.append(SqliteStorage::displayName());
352   }
353   // TODO: temporary
354   // storageProviders.append("MySQL");
355   
356   return storageProviders;
357 }