The Quassel Core now remembers on exit which networks where connected and which channels
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-07 by the Quassel IRC Team                         *
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 "sqlitestorage.h"
25 #include "util.h"
26
27 #include <QMetaObject>
28 #include <QMetaMethod>
29
30 Core *Core::instanceptr = 0;
31
32 Core *Core::instance() {
33   if(instanceptr) return instanceptr;
34   instanceptr = new Core();
35   instanceptr->init();
36   return instanceptr;
37 }
38
39 void Core::destroy() {
40   delete instanceptr;
41   instanceptr = 0;
42 }
43
44 Core::Core() {
45   storage = NULL;
46 }
47
48 void Core::init() {
49   CoreSettings s;
50   configured = false;
51
52   QVariantMap dbSettings = s.databaseSettings().toMap();
53   QString hname = dbSettings["Type"].toString().toLower();
54   hname[0] = hname[0].toUpper();
55   hname = "initStorage" + hname;
56   if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured),  Q_ARG(QVariantMap, dbSettings), Q_ARG(bool, false))) {
57     qWarning("No database backend configured.");
58   }
59   
60   if (!configured) {
61     qWarning("Core is currently not configured!");
62   }
63     
64   connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
65   startListening(s.port());
66   guiUser = 0;
67
68 }
69
70 bool Core::initStorageSqlite(QVariantMap dbSettings, bool setup) {
71   if (!SqliteStorage::isAvailable()) {
72     qFatal("Sqlite is currently required! Please make sure your Qt library has sqlite support enabled.");
73   }
74   if (storage) {
75     qDebug() << "Deleting old storage object.";
76     delete storage;
77     storage = NULL;
78   }
79   
80   storage = new SqliteStorage();
81   if (setup && !storage->setup(dbSettings)) {
82     return false;
83   }
84   
85   return storage->init(dbSettings);
86 }
87
88 Core::~Core() {
89   qDeleteAll(sessions);
90   if (storage) {
91     delete storage;
92     storage = NULL;
93   }
94 }
95
96 void Core::restoreState() {
97   Q_ASSERT(!instance()->sessions.count());
98   CoreSettings s;
99   QList<QVariant> users = s.coreState().toList();
100   if(users.count() > 0) {
101     qDebug() << "Restoring previous core state...";
102     foreach(QVariant v, users) {
103       QVariantMap m = v.toMap();
104       if(m.contains("UserId")) {
105         CoreSession *sess = createSession(m["UserId"].toUInt());
106         sess->restoreState(m["State"]);
107       }
108     }
109   }
110 }
111
112 void Core::saveState() {
113   CoreSettings s;
114   QList<QVariant> users;
115   foreach(CoreSession *sess, instance()->sessions.values()) {
116     QVariantMap m;
117     m["UserId"] = sess->userId();
118     m["State"] = sess->state();
119     users << m;
120   }
121   s.setCoreState(users);
122 }
123
124 CoreSession *Core::session(UserId uid) {
125   Core *core = instance();
126   if(core->sessions.contains(uid)) return core->sessions[uid];
127   else return 0;
128 }
129
130 CoreSession *Core::localSession() {
131   Core *core = instance();
132   if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
133   else return 0;
134 }
135
136 CoreSession *Core::createSession(UserId uid) {
137   Core *core = instance();
138   Q_ASSERT(!core->sessions.contains(uid));
139   CoreSession *sess = new CoreSession(uid, core->storage);
140   core->sessions[uid] = sess;
141   return sess;
142 }
143
144 bool Core::startListening(uint port) {
145   if(!server.listen(QHostAddress::Any, port)) {
146     qWarning(QString(QString("Could not open GUI client port %1: %2").arg(port).arg(server.errorString())).toAscii());
147     return false;
148   }
149   qDebug() << "Listening for GUI clients on port" << server.serverPort();
150   return true;
151 }
152
153 void Core::stopListening() {
154   server.close();
155   qDebug() << "No longer listening for GUI clients.";
156 }
157
158 void Core::incomingConnection() {
159   // TODO implement SSL
160   while (server.hasPendingConnections()) {
161     QTcpSocket *socket = server.nextPendingConnection();
162     connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
163     connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
164     blockSizes.insert(socket, (quint32)0);
165     qDebug() << "Client connected from " << socket->peerAddress().toString();
166     
167     if (!configured) {
168       server.close();
169       qDebug() << "Closing server for basic setup.";
170     }
171   }
172 }
173
174 void Core::clientHasData() {
175   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
176   Q_ASSERT(socket && blockSizes.contains(socket));
177   quint32 bsize = blockSizes.value(socket);
178   QVariant item;
179   if(readDataFromDevice(socket, bsize, item)) {
180     // we need to auth the client
181     try {
182       QVariantMap msg = item.toMap();
183       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
184         throw Exception("GUI client version mismatch");
185       }
186       if (configured) {
187         processClientInit(socket, msg);
188       } else {
189         processCoreSetup(socket, msg);
190       }
191     } catch(Storage::AuthError) {
192       qWarning() << "Authentification error!";  // FIXME: send auth error to client
193       socket->close();
194       return;
195     } catch(Exception e) {
196       qWarning() << "Client init error:" << e.msg();
197       socket->close();
198       return;
199     }
200   }
201   blockSizes[socket] = bsize = 0;  // FIXME blockSizes aufr�um0rn!
202 }
203
204 // FIXME: no longer called, since connection handling is now in SignalProxy
205 // No, it is called as long as core is not configured. (kaffeedoktor)
206 void Core::clientDisconnected() {
207   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
208   blockSizes.remove(socket);
209   qDebug() << "Client disconnected.";
210   
211   // make server listen again if still not configured
212   if (!configured) {
213     startListening();
214   }
215   
216   // TODO remove unneeded sessions - if necessary/possible...
217 }
218
219 QVariant Core::connectLocalClient(QString user, QString passwd) {
220   UserId uid = instance()->storage->validateUser(user, passwd);
221   QVariant reply = instance()->initSession(uid);
222   instance()->guiUser = uid;
223   qDebug() << "Local client connected.";
224   return reply;
225 }
226
227 void Core::disconnectLocalClient() {
228   qDebug() << "Local client disconnected.";
229   instance()->guiUser = 0;
230 }
231
232 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
233   // Auth
234   QVariantMap reply;
235   UserId uid = storage->validateUser(msg["User"].toString(), msg["Password"].toString()); // throws exception if this failed
236   reply["StartWizard"] = false;
237   reply["Reply"] = initSession(uid);
238   disconnect(socket, 0, this, 0);
239   sessions[uid]->addClient(socket);
240   qDebug() << "Client initialized successfully.";
241   writeDataToDevice(socket, reply);
242 }
243
244 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
245   if(msg["HasSettings"].toBool()) {
246     QVariantMap auth;
247     auth["User"] = msg["User"];
248     auth["Password"] = msg["Password"];
249     msg.remove("User");
250     msg.remove("Password");
251     qDebug() << "Initializing storage provider" << msg["Type"].toString();
252     QString hname = msg["Type"].toString().toLower();
253     hname[0] = hname[0].toUpper();
254     hname = "initStorage" + hname;
255     if (!QMetaObject::invokeMethod(this, hname.toAscii(), Q_RETURN_ARG(bool, configured),  Q_ARG(QVariantMap, msg), Q_ARG(bool, true))) {
256       qWarning("No database backend configured.");
257     }
258     if (!configured) {
259       // notify client to start wizard again
260       qWarning("Core is currently not configured!");
261       QVariantMap reply;
262       reply["StartWizard"] = true;
263       reply["StorageProviders"] = availableStorageProviders();
264       writeDataToDevice(socket, reply);
265     } else {
266       // write coresettings
267       CoreSettings s;
268       s.setDatabaseSettings(msg);
269       // write admin user to database & make the core listen again to connections
270       storage->addUser(auth["User"].toString(), auth["Password"].toString());
271       startListening();
272       // continue the normal procedure
273       processClientInit(socket, auth);
274     }
275   } else {
276     // notify client to start wizard
277     QVariantMap reply;
278     reply["StartWizard"] = true;
279     reply["StorageProviders"] = availableStorageProviders();
280     writeDataToDevice(socket, reply);
281   }
282 }
283
284 QVariant Core::initSession(UserId uid) {
285   // Find or create session for validated user
286   CoreSession *sess;
287   if(sessions.contains(uid))
288     sess = sessions[uid];
289   else
290     sess = createSession(uid);
291   QVariantMap reply;
292   reply["SessionState"] = sess->sessionState();
293   return reply;
294 }
295
296 QStringList Core::availableStorageProviders() {
297   QStringList storageProviders;
298   if (SqliteStorage::isAvailable()) {
299     storageProviders.append(SqliteStorage::displayName());
300   }
301   // TODO: temporary
302   storageProviders.append("MySQL");
303   
304   return storageProviders;
305 }