Fix license headers: Quassel IRC Team -> Quassel Project, 2007 -> 2008
[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 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 = 0;
46 }
47
48 void Core::init() {
49
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.
53   CoreSettings cs;
54   QVariant foo = cs.databaseSettings();
55   if(!foo.isValid()) {
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");
59     if(bar.isValid()) {
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)));
69       }
70       os.endGroup();
71
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));
76       }
77       foreach(QString group, os.childGroups()) {
78         ngs.setValue(QString("Accounts/%1/AccountData").arg(group), os.value(QString("%1/AccountData").arg(group)));
79       }
80       os.endGroup();
81       os.beginGroup("Geometry");
82       foreach(QString key, os.childKeys()) {
83         ngs.setValue(QString("UI/%1").arg(key), os.value(key));
84       }
85       os.endGroup();
86
87       ncs.sync();
88       ngs.sync();
89       qWarning() << "Migration successfully finished. You may now delete $HOME/.config/Quassel IRC Development Team/ (on Linux).\n\n";
90     }
91   }
92   // END
93
94   CoreSettings s;
95   configured = false;
96
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.");
103   }
104   
105   if (!configured) {
106     qWarning("Core is currently not configured!");
107   }
108     
109   connect(&server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
110   startListening(s.port());
111   guiUser = 0;
112
113 }
114
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.");
118   }
119   if (storage) {
120     qDebug() << "Deleting old storage object.";
121     delete storage;
122     storage = NULL;
123   }
124   
125   storage = new SqliteStorage();
126   if (setup && !storage->setup(dbSettings)) {
127     return false;
128   }
129   
130   return storage->init(dbSettings);
131 }
132
133 Core::~Core() {
134   qDeleteAll(sessions);
135   if (storage) {
136     delete storage;
137     storage = NULL;
138   }
139 }
140
141 void Core::restoreState() {
142   Q_ASSERT(!instance()->sessions.count());
143   CoreSettings s;
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"]);
152       }
153     }
154   }
155 }
156
157 void Core::saveState() {
158   CoreSettings s;
159   QList<QVariant> users;
160   foreach(CoreSession *sess, instance()->sessions.values()) {
161     QVariantMap m;
162     m["UserId"] = sess->userId();
163     m["State"] = sess->state();
164     users << m;
165   }
166   s.setCoreState(users);
167 }
168
169 CoreSession *Core::session(UserId uid) {
170   Core *core = instance();
171   if(core->sessions.contains(uid)) return core->sessions[uid];
172   else return 0;
173 }
174
175 CoreSession *Core::localSession() {
176   Core *core = instance();
177   if(core->guiUser && core->sessions.contains(core->guiUser)) return core->sessions[core->guiUser];
178   else return 0;
179 }
180
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;
186   return sess;
187 }
188
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());
192     return false;
193   }
194   qDebug() << "Listening for GUI clients on port" << server.serverPort();
195   return true;
196 }
197
198 void Core::stopListening() {
199   server.close();
200   qDebug() << "No longer listening for GUI clients.";
201 }
202
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();
211     
212     if (!configured) {
213       server.close();
214       qDebug() << "Closing server for basic setup.";
215     }
216   }
217 }
218
219 void Core::clientHasData() {
220   QTcpSocket *socket = dynamic_cast<QTcpSocket*>(sender());
221   Q_ASSERT(socket && blockSizes.contains(socket));
222   quint32 bsize = blockSizes.value(socket);
223   QVariant item;
224   if(SignalProxy::readDataFromDevice(socket, bsize, item)) {
225     // we need to auth the client
226     try {
227       QVariantMap msg = item.toMap();
228       if (msg["GuiProtocol"].toUInt() != GUI_PROTOCOL) {
229         throw Exception("GUI client version mismatch");
230       }
231       if (configured) {
232         processClientInit(socket, msg);
233       } else {
234         processCoreSetup(socket, msg);
235       }
236     } catch(Storage::AuthError) {
237       qWarning() << "Authentification error!";  // FIXME: send auth error to client
238       socket->close();
239       return;
240     } catch(Exception e) {
241       qWarning() << "Client init error:" << e.msg();
242       socket->close();
243       return;
244     }
245   }
246   blockSizes[socket] = bsize = 0;  // FIXME blockSizes aufr´┐Żum0rn!
247 }
248
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.";
255   
256   // make server listen again if still not configured
257   if (!configured) {
258     startListening();
259   }
260   
261   // TODO remove unneeded sessions - if necessary/possible...
262 }
263
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.";
269   return reply;
270 }
271
272 void Core::disconnectLocalClient() {
273   qDebug() << "Local client disconnected.";
274   instance()->guiUser = 0;
275 }
276
277 void Core::processClientInit(QTcpSocket *socket, const QVariantMap &msg) {
278   // Auth
279   QVariantMap reply;
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);
287 }
288
289 void Core::processCoreSetup(QTcpSocket *socket, QVariantMap &msg) {
290   if(msg["HasSettings"].toBool()) {
291     QVariantMap auth;
292     auth["User"] = msg["User"];
293     auth["Password"] = msg["Password"];
294     msg.remove("User");
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.");
302     }
303     if (!configured) {
304       // notify client to start wizard again
305       qWarning("Core is currently not configured!");
306       QVariantMap reply;
307       reply["StartWizard"] = true;
308       reply["StorageProviders"] = availableStorageProviders();
309       SignalProxy::writeDataToDevice(socket, reply);
310     } else {
311       // write coresettings
312       CoreSettings s;
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());
316       startListening();
317       // continue the normal procedure
318       processClientInit(socket, auth);
319     }
320   } else {
321     // notify client to start wizard
322     QVariantMap reply;
323     reply["StartWizard"] = true;
324     reply["StorageProviders"] = availableStorageProviders();
325     SignalProxy::writeDataToDevice(socket, reply);
326   }
327 }
328
329 QVariant Core::initSession(UserId uid) {
330   // Find or create session for validated user
331   CoreSession *sess;
332   if(sessions.contains(uid))
333     sess = sessions[uid];
334   else
335     sess = createSession(uid);
336   QVariantMap reply;
337   reply["SessionState"] = sess->sessionState();
338   return reply;
339 }
340
341 QStringList Core::availableStorageProviders() {
342   QStringList storageProviders;
343   if (SqliteStorage::isAvailable()) {
344     storageProviders.append(SqliteStorage::displayName());
345   }
346   // TODO: temporary
347   storageProviders.append("MySQL");
348   
349   return storageProviders;
350 }