_Almost_ finished the identity dialog (which is still not used and not visible yet...
[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   }
166 }
167
168 void Core::saveState() {
169   CoreSettings s;
170   QList<QVariant> users;
171   foreach(CoreSession *sess, instance()->sessions.values()) {
172     QVariantMap m;
173     m["UserId"] = sess->userId();
174     m["State"] = sess->state();
175     users << m;
176   }
177   s.setCoreState(users);
178 }
179
180 CoreSession *Core::session(UserId uid) {
181   Core *core = instance();
182   if(core->sessions.contains(uid)) return core->sessions[uid];
183   else return 0;
184 }
185
186 CoreSession *Core::localSession() {
187   Core *core = instance();
188   if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
189   else return 0;
190 }
191
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;
197   return sess;
198 }
199
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());
203     return false;
204   }
205   qDebug() << "Listening for GUI clients on port" << server.serverPort();
206   return true;
207 }
208
209 void Core::stopListening() {
210   server.close();
211   qDebug() << "No longer listening for GUI clients.";
212 }
213
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();
222     
223     if (!configured) {
224       server.close();
225       qDebug() << "Closing server for basic setup.";
226     }
227   }
228 }
229
230 void Core::clientHasData() {
231   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
232   Q_ASSERT(socket && blockSizes.contains(socket));
233   quint32 bsize = blockSizes.value(socket);
234   QVariant item;
235   if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
236     // we need to auth the client
237     try {
238       QVariantMap msg = item.toMap();
239       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
240         throw Exception("GUI client version mismatch");
241       }
242       if (configured) {
243         processClientInit(socket, msg);
244       } else {
245         processCoreSetup(socket, msg);
246       }
247     } catch(Storage::AuthError) {
248       qWarning() << "Authentification error!";  // FIXME: send auth error to client
249       socket->close();
250       return;
251     } catch(Exception e) {
252       qWarning() << "Client init error:" << e.msg();
253       socket->close();
254       return;
255     }
256   }
257   blockSizes[socket] = bsize = 0;  // FIXME blockSizes aufr�um0rn!
258 }
259
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.";
266   
267   // make server listen again if still not configured
268   if (!configured) {
269     startListening();
270   }
271   
272   // TODO remove unneeded sessions - if necessary/possible...
273 }
274
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.";
280   return reply;
281 }
282
283 void Core::disconnectLocalClient() {
284   qDebug() << "Local client disconnected.";
285   instance()->guiUser = 0;
286 }
287
288 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
289   // Auth
290   QVariantMap reply;
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);
298 }
299
300 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
301   if(msg["HasSettings"].toBool()) {
302     QVariantMap auth;
303     auth["User"] = msg["User"];
304     auth["Password"] = msg["Password"];
305     msg.remove("User");
306     msg.remove("Password");
307     qDebug() << "Initializing storage provider" << msg["Type"].toString();
308
309     if(!initStorage(msg, true)) {
310       // notify client to start wizard again
311       qWarning("Core is currently not configured!");
312       QVariantMap reply;
313       reply["StartWizard"] = true;
314       reply["StorageProviders"] = availableStorageProviders();
315       SignalProxy::writeDataToDevice(socket, reply);
316     } else {
317       // write coresettings
318       CoreSettings s;
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());
322       startListening();
323       // continue the normal procedure
324       processClientInit(socket, auth);
325     }
326   } else {
327     // notify client to start wizard
328     QVariantMap reply;
329     reply["StartWizard"] = true;
330     reply["StorageProviders"] = availableStorageProviders();
331     SignalProxy::writeDataToDevice(socket, reply);
332   }
333 }
334
335 QVariant Core::initSession(UserId uid) {
336   // Find or create session for validated user
337   CoreSession *sess;
338   if(sessions.contains(uid))
339     sess = sessions[uid];
340   else
341     sess = createSession(uid);
342   QVariantMap reply;
343   reply["SessionState"] = sess->sessionState();
344   return reply;
345 }
346
347 QStringList Core::availableStorageProviders() {
348   QStringList storageProviders;
349   if (SqliteStorage::isAvailable()) {
350     storageProviders.append(SqliteStorage::displayName());
351   }
352   // TODO: temporary
353   // storageProviders.append("MySQL");
354   
355   return storageProviders;
356 }