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