7068f84ec28b67bf30bff7a7437fbde5acd3194f
[quassel.git] / src / core / core.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2014 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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QCoreApplication>
22
23 #include "core.h"
24 #include "coreauthhandler.h"
25 #include "coresession.h"
26 #include "coresettings.h"
27 #include "logger.h"
28 #include "internalpeer.h"
29 #include "network.h"
30 #include "postgresqlstorage.h"
31 #include "quassel.h"
32 #include "sqlitestorage.h"
33 #include "util.h"
34
35 // migration related
36 #include <QFile>
37 #ifdef Q_OS_WIN
38 #  include <windows.h>
39 #else
40 #  include <unistd.h>
41 #  include <termios.h>
42 #endif /* Q_OS_WIN */
43
44 #ifdef HAVE_UMASK
45 #  include <sys/types.h>
46 #  include <sys/stat.h>
47 #endif /* HAVE_UMASK */
48
49 // ==============================
50 //  Custom Events
51 // ==============================
52 const int Core::AddClientEventId = QEvent::registerEventType();
53
54 class AddClientEvent : public QEvent
55 {
56 public:
57     AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
58     RemotePeer *peer;
59     UserId userId;
60 };
61
62
63 // ==============================
64 //  Core
65 // ==============================
66 Core *Core::instanceptr = 0;
67
68 Core *Core::instance()
69 {
70     if (instanceptr) return instanceptr;
71     instanceptr = new Core();
72     instanceptr->init();
73     return instanceptr;
74 }
75
76
77 void Core::destroy()
78 {
79     delete instanceptr;
80     instanceptr = 0;
81 }
82
83
84 Core::Core()
85     : QObject(),
86       _storage(0)
87 {
88 #ifdef HAVE_UMASK
89     umask(S_IRWXG | S_IRWXO);
90 #endif
91     _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
92
93     Quassel::loadTranslation(QLocale::system());
94
95     // FIXME: MIGRATION 0.3 -> 0.4: Move database and core config to new location
96     // Move settings, note this does not delete the old files
97 #ifdef Q_OS_MAC
98     QSettings newSettings("quassel-irc.org", "quasselcore");
99 #else
100
101 # ifdef Q_OS_WIN
102     QSettings::Format format = QSettings::IniFormat;
103 # else
104     QSettings::Format format = QSettings::NativeFormat;
105 # endif
106     QString newFilePath = Quassel::configDirPath() + "quasselcore"
107                           + ((format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
108     QSettings newSettings(newFilePath, format);
109 #endif /* Q_OS_MAC */
110
111     if (newSettings.value("Config/Version").toUInt() == 0) {
112 #   ifdef Q_OS_MAC
113         QString org = "quassel-irc.org";
114 #   else
115         QString org = "Quassel Project";
116 #   endif
117         QSettings oldSettings(org, "Quassel Core");
118         if (oldSettings.allKeys().count()) {
119             qWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
120             foreach(QString key, oldSettings.allKeys())
121             newSettings.setValue(key, oldSettings.value(key));
122             newSettings.setValue("Config/Version", 1);
123             qWarning() << "*   Your core settings have been migrated to" << newSettings.fileName();
124
125 #ifndef Q_OS_MAC /* we don't need to move the db and cert for mac */
126 #ifdef Q_OS_WIN
127             QString quasselDir = qgetenv("APPDATA") + "/quassel/";
128 #elif defined Q_OS_MAC
129             QString quasselDir = QDir::homePath() + "/Library/Application Support/Quassel/";
130 #else
131             QString quasselDir = QDir::homePath() + "/.quassel/";
132 #endif
133
134             QFileInfo info(Quassel::configDirPath() + "quassel-storage.sqlite");
135             if (!info.exists()) {
136                 // move database, if we found it
137                 QFile oldDb(quasselDir + "quassel-storage.sqlite");
138                 if (oldDb.exists()) {
139                     bool success = oldDb.rename(Quassel::configDirPath() + "quassel-storage.sqlite");
140                     if (success)
141                         qWarning() << "*   Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
142                     else
143                         qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
144                 }
145             }
146             // move certificate
147             QFileInfo certInfo(quasselDir + "quasselCert.pem");
148             if (certInfo.exists()) {
149                 QFile cert(quasselDir + "quasselCert.pem");
150                 bool success = cert.rename(Quassel::configDirPath() + "quasselCert.pem");
151                 if (success)
152                     qWarning() << "*   Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
153                 else
154                     qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
155             }
156 #endif /* !Q_OS_MAC */
157             qWarning() << "*** Migration completed.\n\n";
158         }
159     }
160     // MIGRATION end
161
162     // check settings version
163     // so far, we only have 1
164     CoreSettings s;
165     if (s.version() != 1) {
166         qCritical() << "Invalid core settings version, terminating!";
167         exit(EXIT_FAILURE);
168     }
169
170     registerStorageBackends();
171
172     connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
173     _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
174 }
175
176
177 void Core::init()
178 {
179     CoreSettings cs;
180     // legacy
181     QVariantMap dbsettings = cs.storageSettings().toMap();
182     _configured = initStorage(dbsettings.value("Backend").toString(), dbsettings.value("ConnectionProperties").toMap());
183
184     if (Quassel::isOptionSet("select-backend")) {
185         selectBackend(Quassel::optionValue("select-backend"));
186         exit(0);
187     }
188
189     if (!_configured) {
190         if (!_storageBackends.count()) {
191             qWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
192             qWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
193                                         "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
194                                         "to work."));
195             exit(1); // TODO make this less brutal (especially for mono client -> popup)
196         }
197         qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
198     }
199
200     if (Quassel::isOptionSet("add-user")) {
201         createUser();
202         exit(0);
203     }
204
205     if (Quassel::isOptionSet("change-userpass")) {
206         changeUserPass(Quassel::optionValue("change-userpass"));
207         exit(0);
208     }
209
210     connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
211     connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
212     if (!startListening()) exit(1);  // TODO make this less brutal
213
214     if (Quassel::isOptionSet("oidentd"))
215         _oidentdConfigGenerator = new OidentdConfigGenerator(this);
216 }
217
218
219 Core::~Core()
220 {
221     // FIXME do we need more cleanup for handlers?
222     foreach(CoreAuthHandler *handler, _connectingClients) {
223         handler->deleteLater(); // disconnect non authed clients
224     }
225     qDeleteAll(sessions);
226     qDeleteAll(_storageBackends);
227 }
228
229
230 /*** Session Restore ***/
231
232 void Core::saveState()
233 {
234     CoreSettings s;
235     QVariantMap state;
236     QVariantList activeSessions;
237     foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
238     state["CoreStateVersion"] = 1;
239     state["ActiveSessions"] = activeSessions;
240     s.setCoreState(state);
241 }
242
243
244 void Core::restoreState()
245 {
246     if (!instance()->_configured) {
247         // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
248         return;
249     }
250     if (instance()->sessions.count()) {
251         qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
252         return;
253     }
254     CoreSettings s;
255     /* We don't check, since we are at the first version since switching to Git
256     uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
257     if(statever < 1) {
258       qWarning() << qPrintable(tr("Core state too old, ignoring..."));
259       return;
260     }
261     */
262
263     QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
264     if (activeSessions.count() > 0) {
265         quInfo() << "Restoring previous core state...";
266         foreach(QVariant v, activeSessions) {
267             UserId user = v.value<UserId>();
268             instance()->createSession(user, true);
269         }
270     }
271 }
272
273
274 /*** Core Setup ***/
275
276 QString Core::setup(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
277 {
278     return instance()->setupCore(adminUser, adminPassword, backend, setupData);
279 }
280
281
282 QString Core::setupCore(const QString &adminUser, const QString &adminPassword, const QString &backend, const QVariantMap &setupData)
283 {
284     if (_configured)
285         return tr("Core is already configured! Not configuring again...");
286
287     if (adminUser.isEmpty() || adminPassword.isEmpty()) {
288         return tr("Admin user or password not set.");
289     }
290     if (!(_configured = initStorage(backend, setupData, true))) {
291         return tr("Could not setup storage!");
292     }
293
294     saveBackendSettings(backend, setupData);
295
296     quInfo() << qPrintable(tr("Creating admin user..."));
297     _storage->addUser(adminUser, adminPassword);
298     startListening(); // TODO check when we need this
299     return QString();
300 }
301
302
303 QString Core::setupCoreForInternalUsage()
304 {
305     Q_ASSERT(!_storageBackends.isEmpty());
306
307     qsrand(QDateTime::currentDateTime().toTime_t());
308     int pass = 0;
309     for (int i = 0; i < 10; i++) {
310         pass *= 10;
311         pass += qrand() % 10;
312     }
313
314     // mono client currently needs sqlite
315     return setupCore("AdminUser", QString::number(pass), "SQLite", QVariantMap());
316 }
317
318
319 /*** Storage Handling ***/
320 void Core::registerStorageBackends()
321 {
322     // Register storage backends here!
323     registerStorageBackend(new SqliteStorage(this));
324     registerStorageBackend(new PostgreSqlStorage(this));
325 }
326
327
328 bool Core::registerStorageBackend(Storage *backend)
329 {
330     if (backend->isAvailable()) {
331         _storageBackends[backend->displayName()] = backend;
332         return true;
333     }
334     else {
335         backend->deleteLater();
336         return false;
337     }
338 }
339
340
341 void Core::unregisterStorageBackends()
342 {
343     foreach(Storage *s, _storageBackends.values()) {
344         s->deleteLater();
345     }
346     _storageBackends.clear();
347 }
348
349
350 void Core::unregisterStorageBackend(Storage *backend)
351 {
352     _storageBackends.remove(backend->displayName());
353     backend->deleteLater();
354 }
355
356
357 // old db settings:
358 // "Type" => "sqlite"
359 bool Core::initStorage(const QString &backend, const QVariantMap &settings, bool setup)
360 {
361     _storage = 0;
362
363     if (backend.isEmpty()) {
364         return false;
365     }
366
367     Storage *storage = 0;
368     if (_storageBackends.contains(backend)) {
369         storage = _storageBackends[backend];
370     }
371     else {
372         qCritical() << "Selected storage backend is not available:" << backend;
373         return false;
374     }
375
376     Storage::State storageState = storage->init(settings);
377     switch (storageState) {
378     case Storage::NeedsSetup:
379         if (!setup)
380             return false;  // trigger setup process
381         if (storage->setup(settings))
382             return initStorage(backend, settings, false);
383     // if initialization wasn't successful, we quit to keep from coming up unconfigured
384     case Storage::NotAvailable:
385         qCritical() << "FATAL: Selected storage backend is not available:" << backend;
386         exit(EXIT_FAILURE);
387     case Storage::IsReady:
388         // delete all other backends
389         _storageBackends.remove(backend);
390         unregisterStorageBackends();
391         connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
392     }
393     _storage = storage;
394     return true;
395 }
396
397
398 void Core::syncStorage()
399 {
400     if (_storage)
401         _storage->sync();
402 }
403
404
405 /*** Storage Access ***/
406 bool Core::createNetwork(UserId user, NetworkInfo &info)
407 {
408     NetworkId networkId = instance()->_storage->createNetwork(user, info);
409     if (!networkId.isValid())
410         return false;
411
412     info.networkId = networkId;
413     return true;
414 }
415
416
417 /*** Network Management ***/
418
419 bool Core::sslSupported()
420 {
421 #ifdef HAVE_SSL
422     SslServer *sslServer = qobject_cast<SslServer *>(&instance()->_server);
423     return sslServer && sslServer->isCertValid();
424 #else
425     return false;
426 #endif
427 }
428
429
430 bool Core::startListening()
431 {
432     // in mono mode we only start a local port if a port is specified in the cli call
433     if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
434         return true;
435
436     bool success = false;
437     uint port = Quassel::optionValue("port").toUInt();
438
439     const QString listen = Quassel::optionValue("listen");
440     const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
441     if (listen_list.size() > 0) {
442         foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
443             QHostAddress addr;
444             if (!addr.setAddress(listen_term)) {
445                 qCritical() << qPrintable(
446                     tr("Invalid listen address %1")
447                     .arg(listen_term)
448                     );
449             }
450             else {
451                 switch (addr.protocol()) {
452                 case QAbstractSocket::IPv6Protocol:
453                     if (_v6server.listen(addr, port)) {
454                         quInfo() << qPrintable(
455                             tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
456                             .arg(addr.toString())
457                             .arg(_v6server.serverPort())
458                             .arg(Quassel::buildInfo().protocolVersion)
459                             );
460                         success = true;
461                     }
462                     else
463                         quWarning() << qPrintable(
464                             tr("Could not open IPv6 interface %1:%2: %3")
465                             .arg(addr.toString())
466                             .arg(port)
467                             .arg(_v6server.errorString()));
468                     break;
469                 case QAbstractSocket::IPv4Protocol:
470                     if (_server.listen(addr, port)) {
471                         quInfo() << qPrintable(
472                             tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
473                             .arg(addr.toString())
474                             .arg(_server.serverPort())
475                             .arg(Quassel::buildInfo().protocolVersion)
476                             );
477                         success = true;
478                     }
479                     else {
480                         // if v6 succeeded on Any, the port will be already in use - don't display the error then
481                         if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
482                             quWarning() << qPrintable(
483                                 tr("Could not open IPv4 interface %1:%2: %3")
484                                 .arg(addr.toString())
485                                 .arg(port)
486                                 .arg(_server.errorString()));
487                     }
488                     break;
489                 default:
490                     qCritical() << qPrintable(
491                         tr("Invalid listen address %1, unknown network protocol")
492                         .arg(listen_term)
493                         );
494                     break;
495                 }
496             }
497         }
498     }
499     if (!success)
500         quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
501
502     return success;
503 }
504
505
506 void Core::stopListening(const QString &reason)
507 {
508     bool wasListening = false;
509     if (_server.isListening()) {
510         wasListening = true;
511         _server.close();
512     }
513     if (_v6server.isListening()) {
514         wasListening = true;
515         _v6server.close();
516     }
517     if (wasListening) {
518         if (reason.isEmpty())
519             quInfo() << "No longer listening for GUI clients.";
520         else
521             quInfo() << qPrintable(reason);
522     }
523 }
524
525
526 void Core::incomingConnection()
527 {
528     QTcpServer *server = qobject_cast<QTcpServer *>(sender());
529     Q_ASSERT(server);
530     while (server->hasPendingConnections()) {
531         QTcpSocket *socket = server->nextPendingConnection();
532
533         CoreAuthHandler *handler = new CoreAuthHandler(socket, this);
534         _connectingClients.insert(handler);
535
536         connect(handler, SIGNAL(disconnected()), SLOT(clientDisconnected()));
537         connect(handler, SIGNAL(socketError(QAbstractSocket::SocketError,QString)), SLOT(socketError(QAbstractSocket::SocketError,QString)));
538         connect(handler, SIGNAL(handshakeComplete(RemotePeer*,UserId)), SLOT(setupClientSession(RemotePeer*,UserId)));
539
540         quInfo() << qPrintable(tr("Client connected from"))  << qPrintable(socket->peerAddress().toString());
541
542         if (!_configured) {
543             stopListening(tr("Closing server for basic setup."));
544         }
545     }
546 }
547
548
549 // Potentially called during the initialization phase (before handing the connection off to the session)
550 void Core::clientDisconnected()
551 {
552     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
553     Q_ASSERT(handler);
554
555     quInfo() << qPrintable(tr("Non-authed client disconnected:")) << qPrintable(handler->socket()->peerAddress().toString());
556     _connectingClients.remove(handler);
557     handler->deleteLater();
558
559     // make server listen again if still not configured
560     if (!_configured) {
561         startListening();
562     }
563
564     // TODO remove unneeded sessions - if necessary/possible...
565     // Suggestion: kill sessions if they are not connected to any network and client.
566 }
567
568
569 void Core::setupClientSession(RemotePeer *peer, UserId uid)
570 {
571     CoreAuthHandler *handler = qobject_cast<CoreAuthHandler *>(sender());
572     Q_ASSERT(handler);
573
574     // From now on everything is handled by the client session
575     disconnect(handler, 0, this, 0);
576     _connectingClients.remove(handler);
577     handler->deleteLater();
578
579     // Find or create session for validated user
580     SessionThread *session;
581     if (sessions.contains(uid)) {
582         session = sessions[uid];
583     }
584     else {
585         session = createSession(uid);
586         if (!session) {
587             qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(peer->description());
588             peer->close();
589             peer->deleteLater();
590             return;
591         }
592     }
593
594     // as we are currently handling an event triggered by incoming data on this socket
595     // it is unsafe to directly move the socket to the client thread.
596     QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
597 }
598
599
600 void Core::customEvent(QEvent *event)
601 {
602     if (event->type() == AddClientEventId) {
603         AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
604         addClientHelper(addClientEvent->peer, addClientEvent->userId);
605         return;
606     }
607 }
608
609
610 void Core::addClientHelper(RemotePeer *peer, UserId uid)
611 {
612     // Find or create session for validated user
613     if (!sessions.contains(uid)) {
614         qWarning() << qPrintable(tr("Could not find a session for client:")) << qPrintable(peer->description());
615         peer->close();
616         peer->deleteLater();
617         return;
618     }
619
620     SessionThread *session = sessions[uid];
621     session->addClient(peer);
622 }
623
624
625 void Core::setupInternalClientSession(InternalPeer *clientPeer)
626 {
627     if (!_configured) {
628         stopListening();
629         setupCoreForInternalUsage();
630     }
631
632     UserId uid;
633     if (_storage) {
634         uid = _storage->internalUser();
635     }
636     else {
637         qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
638         return;
639     }
640
641     InternalPeer *corePeer = new InternalPeer(this);
642     corePeer->setPeer(clientPeer);
643     clientPeer->setPeer(corePeer);
644
645     // Find or create session for validated user
646     SessionThread *sessionThread;
647     if (sessions.contains(uid))
648         sessionThread = sessions[uid];
649     else
650         sessionThread = createSession(uid);
651
652     sessionThread->addClient(corePeer);
653 }
654
655
656 SessionThread *Core::createSession(UserId uid, bool restore)
657 {
658     if (sessions.contains(uid)) {
659         qWarning() << "Calling createSession() when a session for the user already exists!";
660         return 0;
661     }
662     SessionThread *sess = new SessionThread(uid, restore, this);
663     sessions[uid] = sess;
664     sess->start();
665     return sess;
666 }
667
668
669 void Core::socketError(QAbstractSocket::SocketError err, const QString &errorString)
670 {
671     qWarning() << QString("Socket error %1: %2").arg(err).arg(errorString);
672 }
673
674
675 QVariantList Core::backendInfo()
676 {
677     QVariantList backends;
678     foreach(const Storage *backend, instance()->_storageBackends.values()) {
679         QVariantMap v;
680         v["DisplayName"] = backend->displayName();
681         v["Description"] = backend->description();
682         v["SetupKeys"] = backend->setupKeys();
683         v["SetupDefaults"] = backend->setupDefaults();
684         backends.append(v);
685     }
686     return backends;
687 }
688
689
690 // migration / backend selection
691 bool Core::selectBackend(const QString &backend)
692 {
693     // reregister all storage backends
694     registerStorageBackends();
695     if (!_storageBackends.contains(backend)) {
696         qWarning() << qPrintable(QString("Core::selectBackend(): unsupported backend: %1").arg(backend));
697         qWarning() << "    supported backends are:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
698         return false;
699     }
700
701     Storage *storage = _storageBackends[backend];
702     QVariantMap settings = promptForSettings(storage);
703
704     Storage::State storageState = storage->init(settings);
705     switch (storageState) {
706     case Storage::IsReady:
707         saveBackendSettings(backend, settings);
708         qWarning() << "Switched backend to:" << qPrintable(backend);
709         qWarning() << "Backend already initialized. Skipping Migration";
710         return true;
711     case Storage::NotAvailable:
712         qCritical() << "Backend is not available:" << qPrintable(backend);
713         return false;
714     case Storage::NeedsSetup:
715         if (!storage->setup(settings)) {
716             qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
717             return false;
718         }
719
720         if (storage->init(settings) != Storage::IsReady) {
721             qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
722             return false;
723         }
724
725         saveBackendSettings(backend, settings);
726         qWarning() << "Switched backend to:" << qPrintable(backend);
727         break;
728     }
729
730     // let's see if we have a current storage object we can migrate from
731     AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
732     AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
733     if (reader && writer) {
734         qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
735         delete _storage;
736         _storage = 0;
737         delete storage;
738         storage = 0;
739         if (reader->migrateTo(writer)) {
740             qDebug() << "Migration finished!";
741             saveBackendSettings(backend, settings);
742             return true;
743         }
744         return false;
745         qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
746     }
747
748     // inform the user why we cannot merge
749     if (!_storage) {
750         qWarning() << "No currently active backend. Skipping migration.";
751     }
752     else if (!reader) {
753         qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
754     }
755     if (writer) {
756         qWarning() << "New backend does not support migration:" << qPrintable(backend);
757     }
758
759     // so we were unable to merge, but let's create a user \o/
760     _storage = storage;
761     createUser();
762     return true;
763 }
764
765
766 void Core::createUser()
767 {
768     QTextStream out(stdout);
769     QTextStream in(stdin);
770     out << "Add a new user:" << endl;
771     out << "Username: ";
772     out.flush();
773     QString username = in.readLine().trimmed();
774
775     disableStdInEcho();
776     out << "Password: ";
777     out.flush();
778     QString password = in.readLine().trimmed();
779     out << endl;
780     out << "Repeat Password: ";
781     out.flush();
782     QString password2 = in.readLine().trimmed();
783     out << endl;
784     enableStdInEcho();
785
786     if (password != password2) {
787         qWarning() << "Passwords don't match!";
788         return;
789     }
790     if (password.isEmpty()) {
791         qWarning() << "Password is empty!";
792         return;
793     }
794
795     if (_configured && _storage->addUser(username, password).isValid()) {
796         out << "Added user " << username << " successfully!" << endl;
797     }
798     else {
799         qWarning() << "Unable to add user:" << qPrintable(username);
800     }
801 }
802
803
804 void Core::changeUserPass(const QString &username)
805 {
806     QTextStream out(stdout);
807     QTextStream in(stdin);
808     UserId userId = _storage->getUserId(username);
809     if (!userId.isValid()) {
810         out << "User " << username << " does not exist." << endl;
811         return;
812     }
813
814     out << "Change password for user: " << username << endl;
815
816     disableStdInEcho();
817     out << "New Password: ";
818     out.flush();
819     QString password = in.readLine().trimmed();
820     out << endl;
821     out << "Repeat Password: ";
822     out.flush();
823     QString password2 = in.readLine().trimmed();
824     out << endl;
825     enableStdInEcho();
826
827     if (password != password2) {
828         qWarning() << "Passwords don't match!";
829         return;
830     }
831     if (password.isEmpty()) {
832         qWarning() << "Password is empty!";
833         return;
834     }
835
836     if (_configured && _storage->updateUser(userId, password)) {
837         out << "Password changed successfully!" << endl;
838     }
839     else {
840         qWarning() << "Failed to change password!";
841     }
842 }
843
844
845 AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
846 {
847     if (!storage)
848         return 0;
849
850     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
851     if (!sqlStorage) {
852         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
853         return 0;
854     }
855
856     return sqlStorage->createMigrationReader();
857 }
858
859
860 AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
861 {
862     if (!storage)
863         return 0;
864
865     AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
866     if (!sqlStorage) {
867         qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
868         return 0;
869     }
870
871     return sqlStorage->createMigrationWriter();
872 }
873
874
875 void Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
876 {
877     QVariantMap dbsettings;
878     dbsettings["Backend"] = backend;
879     dbsettings["ConnectionProperties"] = settings;
880     CoreSettings().setStorageSettings(dbsettings);
881 }
882
883
884 QVariantMap Core::promptForSettings(const Storage *storage)
885 {
886     QVariantMap settings;
887
888     QStringList keys = storage->setupKeys();
889     if (keys.isEmpty())
890         return settings;
891
892     QTextStream out(stdout);
893     QTextStream in(stdin);
894     out << "Default values are in brackets" << endl;
895
896     QVariantMap defaults = storage->setupDefaults();
897     QString value;
898     foreach(QString key, keys) {
899         QVariant val;
900         if (defaults.contains(key)) {
901             val = defaults[key];
902         }
903         out << key;
904         if (!val.toString().isEmpty()) {
905             out << " (" << val.toString() << ")";
906         }
907         out << ": ";
908         out.flush();
909
910         bool noEcho = QString("password").toLower().startsWith(key.toLower());
911         if (noEcho) {
912             disableStdInEcho();
913         }
914         value = in.readLine().trimmed();
915         if (noEcho) {
916             out << endl;
917             enableStdInEcho();
918         }
919
920         if (!value.isEmpty()) {
921             switch (defaults[key].type()) {
922             case QVariant::Int:
923                 val = QVariant(value.toInt());
924                 break;
925             default:
926                 val = QVariant(value);
927             }
928         }
929         settings[key] = val;
930     }
931     return settings;
932 }
933
934
935 #ifdef Q_OS_WIN
936 void Core::stdInEcho(bool on)
937 {
938     HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
939     DWORD mode = 0;
940     GetConsoleMode(hStdin, &mode);
941     if (on)
942         mode |= ENABLE_ECHO_INPUT;
943     else
944         mode &= ~ENABLE_ECHO_INPUT;
945     SetConsoleMode(hStdin, mode);
946 }
947
948
949 #else
950 void Core::stdInEcho(bool on)
951 {
952     termios t;
953     tcgetattr(STDIN_FILENO, &t);
954     if (on)
955         t.c_lflag |= ECHO;
956     else
957         t.c_lflag &= ~ECHO;
958     tcsetattr(STDIN_FILENO, TCSANOW, &t);
959 }
960
961
962 #endif /* Q_OS_WIN */