1 /***************************************************************************
2 * Copyright (C) 2005-2012 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include <QCoreApplication>
24 #include "coresession.h"
25 #include "coresettings.h"
26 #include "internalconnection.h"
27 #include "postgresqlstorage.h"
29 #include "signalproxy.h"
30 #include "sqlitestorage.h"
36 #include "protocols/legacy/legacyconnection.h"
45 #endif /* Q_OS_WIN32 */
48 # include <sys/types.h>
49 # include <sys/stat.h>
50 #endif /* HAVE_UMASK */
52 // ==============================
54 // ==============================
55 const int Core::AddClientEventId = QEvent::registerEventType();
57 class AddClientEvent : public QEvent
60 AddClientEvent(RemoteConnection *connection, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), connection(connection), userId(uid) {}
61 RemoteConnection *connection;
66 // ==============================
68 // ==============================
69 Core *Core::instanceptr = 0;
71 Core *Core::instance()
73 if (instanceptr) return instanceptr;
74 instanceptr = new Core();
91 umask(S_IRWXG | S_IRWXO);
93 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
95 Quassel::loadTranslation(QLocale::system());
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
100 QSettings newSettings("quassel-irc.org", "quasselcore");
104 QSettings::Format format = QSettings::IniFormat;
106 QSettings::Format format = QSettings::NativeFormat;
108 QString newFilePath = Quassel::configDirPath() + "quasselcore"
109 + ((format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
110 QSettings newSettings(newFilePath, format);
111 #endif /* Q_WS_MAC */
113 if (newSettings.value("Config/Version").toUInt() == 0) {
115 QString org = "quassel-irc.org";
117 QString org = "Quassel Project";
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();
127 #ifndef Q_WS_MAC /* we don't need to move the db and cert for mac */
129 QString quasselDir = qgetenv("APPDATA") + "/quassel/";
130 #elif defined Q_WS_MAC
131 QString quasselDir = QDir::homePath() + "/Library/Application Support/Quassel/";
133 QString quasselDir = QDir::homePath() + "/.quassel/";
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");
143 qWarning() << "* Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
145 qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
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");
154 qWarning() << "* Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
156 qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
158 #endif /* !Q_WS_MAC */
159 qWarning() << "*** Migration completed.\n\n";
164 // check settings version
165 // so far, we only have 1
167 if (s.version() != 1) {
168 qCritical() << "Invalid core settings version, terminating!";
172 registerStorageBackends();
174 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
175 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
182 _configured = initStorage(cs.storageSettings().toMap());
184 if (Quassel::isOptionSet("select-backend")) {
185 selectBackend(Quassel::optionValue("select-backend"));
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"
195 exit(1); // TODO make this less brutal (especially for mono client -> popup)
197 qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
200 if (Quassel::isOptionSet("add-user")) {
205 if (Quassel::isOptionSet("change-userpass")) {
206 changeUserPass(Quassel::optionValue("change-userpass"));
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
214 if (Quassel::isOptionSet("oidentd"))
215 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
221 foreach(RemoteConnection *connection, clientInfo.keys()) {
222 connection->close(); // disconnect non authed clients
224 qDeleteAll(sessions);
225 qDeleteAll(_storageBackends);
229 /*** Session Restore ***/
231 void Core::saveState()
235 QVariantList activeSessions;
236 foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
237 state["CoreStateVersion"] = 1;
238 state["ActiveSessions"] = activeSessions;
239 s.setCoreState(state);
243 void Core::restoreState()
245 if (!instance()->_configured) {
246 // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
249 if (instance()->sessions.count()) {
250 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
254 /* We don't check, since we are at the first version since switching to Git
255 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
257 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
262 QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
263 if (activeSessions.count() > 0) {
264 quInfo() << "Restoring previous core state...";
265 foreach(QVariant v, activeSessions) {
266 UserId user = v.value<UserId>();
267 instance()->createSession(user, true);
274 QString Core::setupCoreForInternalUsage()
276 Q_ASSERT(!_storageBackends.isEmpty());
277 QVariantMap setupData;
278 qsrand(QDateTime::currentDateTime().toTime_t());
280 for (int i = 0; i < 10; i++) {
282 pass += qrand() % 10;
284 setupData["AdminUser"] = "AdminUser";
285 setupData["AdminPasswd"] = QString::number(pass);
286 setupData["Backend"] = QString("SQLite"); // mono client currently needs sqlite
287 return setupCore(setupData);
291 QString Core::setupCore(QVariantMap setupData)
293 QString user = setupData.take("AdminUser").toString();
294 QString password = setupData.take("AdminPasswd").toString();
295 if (user.isEmpty() || password.isEmpty()) {
296 return tr("Admin user or password not set.");
298 if (_configured || !(_configured = initStorage(setupData, true))) {
299 return tr("Could not setup storage!");
302 s.setStorageSettings(setupData);
303 quInfo() << qPrintable(tr("Creating admin user..."));
304 _storage->addUser(user, password);
305 startListening(); // TODO check when we need this
310 /*** Storage Handling ***/
311 void Core::registerStorageBackends()
313 // Register storage backends here!
314 registerStorageBackend(new SqliteStorage(this));
315 registerStorageBackend(new PostgreSqlStorage(this));
319 bool Core::registerStorageBackend(Storage *backend)
321 if (backend->isAvailable()) {
322 _storageBackends[backend->displayName()] = backend;
326 backend->deleteLater();
332 void Core::unregisterStorageBackends()
334 foreach(Storage *s, _storageBackends.values()) {
337 _storageBackends.clear();
341 void Core::unregisterStorageBackend(Storage *backend)
343 _storageBackends.remove(backend->displayName());
344 backend->deleteLater();
349 // "Type" => "sqlite"
350 bool Core::initStorage(const QString &backend, QVariantMap settings, bool setup)
354 if (backend.isEmpty()) {
358 Storage *storage = 0;
359 if (_storageBackends.contains(backend)) {
360 storage = _storageBackends[backend];
363 qCritical() << "Selected storage backend is not available:" << backend;
367 Storage::State storageState = storage->init(settings);
368 switch (storageState) {
369 case Storage::NeedsSetup:
371 return false; // trigger setup process
372 if (storage->setup(settings))
373 return initStorage(backend, settings, false);
374 // if setup wasn't successfull we mark the backend as unavailable
375 case Storage::NotAvailable:
376 qCritical() << "Selected storage backend is not available:" << backend;
377 storage->deleteLater();
378 _storageBackends.remove(backend);
381 case Storage::IsReady:
382 // delete all other backends
383 _storageBackends.remove(backend);
384 unregisterStorageBackends();
385 connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
392 bool Core::initStorage(QVariantMap dbSettings, bool setup)
394 return initStorage(dbSettings["Backend"].toString(), dbSettings["ConnectionProperties"].toMap(), setup);
398 void Core::syncStorage()
405 /*** Storage Access ***/
406 bool Core::createNetwork(UserId user, NetworkInfo &info)
408 NetworkId networkId = instance()->_storage->createNetwork(user, info);
409 if (!networkId.isValid())
412 info.networkId = networkId;
417 /*** Network Management ***/
419 bool Core::startListening()
421 // in mono mode we only start a local port if a port is specified in the cli call
422 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
425 bool success = false;
426 uint port = Quassel::optionValue("port").toUInt();
428 const QString listen = Quassel::optionValue("listen");
429 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
430 if (listen_list.size() > 0) {
431 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
433 if (!addr.setAddress(listen_term)) {
434 qCritical() << qPrintable(
435 tr("Invalid listen address %1")
440 switch (addr.protocol()) {
441 case QAbstractSocket::IPv6Protocol:
442 if (_v6server.listen(addr, port)) {
443 quInfo() << qPrintable(
444 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
445 .arg(addr.toString())
446 .arg(_v6server.serverPort())
447 .arg(Quassel::buildInfo().protocolVersion)
452 quWarning() << qPrintable(
453 tr("Could not open IPv6 interface %1:%2: %3")
454 .arg(addr.toString())
456 .arg(_v6server.errorString()));
458 case QAbstractSocket::IPv4Protocol:
459 if (_server.listen(addr, port)) {
460 quInfo() << qPrintable(
461 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
462 .arg(addr.toString())
463 .arg(_server.serverPort())
464 .arg(Quassel::buildInfo().protocolVersion)
469 // if v6 succeeded on Any, the port will be already in use - don't display the error then
470 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
471 quWarning() << qPrintable(
472 tr("Could not open IPv4 interface %1:%2: %3")
473 .arg(addr.toString())
475 .arg(_server.errorString()));
479 qCritical() << qPrintable(
480 tr("Invalid listen address %1, unknown network protocol")
489 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
495 void Core::stopListening(const QString &reason)
497 bool wasListening = false;
498 if (_server.isListening()) {
502 if (_v6server.isListening()) {
507 if (reason.isEmpty())
508 quInfo() << "No longer listening for GUI clients.";
510 quInfo() << qPrintable(reason);
515 void Core::incomingConnection()
517 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
519 while (server->hasPendingConnections()) {
520 QTcpSocket *socket = server->nextPendingConnection();
521 RemoteConnection *connection = new LegacyConnection(socket, this);
523 connect(connection, SIGNAL(disconnected()), SLOT(clientDisconnected()));
524 connect(connection, SIGNAL(dataReceived(QVariant)), SLOT(processClientMessage(QVariant)));
525 connect(connection, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
527 clientInfo.insert(connection, QVariantMap());
528 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
531 stopListening(tr("Closing server for basic setup."));
537 void Core::processClientMessage(const QVariant &data)
539 RemoteConnection *connection = qobject_cast<RemoteConnection *>(sender());
541 qWarning() << Q_FUNC_INFO << "Message not sent by RemoteConnection!";
545 QVariantMap msg = data.toMap();
546 if (!msg.contains("MsgType")) {
547 // Client is way too old, does not even use the current init format
548 qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
553 // OK, so we have at least an init message format we can understand
554 if (msg["MsgType"] == "ClientInit") {
557 // Just version information -- check it!
558 uint ver = msg["ProtocolVersion"].toUInt();
559 if (ver < Quassel::buildInfo().coreNeedsProtocol) {
560 reply["MsgType"] = "ClientInitReject";
561 reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
562 "This core needs at least client/core protocol version %1.<br>"
563 "Please consider upgrading your client.").arg(Quassel::buildInfo().coreNeedsProtocol);
564 connection->writeSocketData(reply);
565 qWarning() << qPrintable(tr("Client")) << connection->description() << qPrintable(tr("too old, rejecting."));
570 reply["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
571 reply["CoreVersion"] = Quassel::buildInfo().fancyVersionString;
572 reply["CoreDate"] = Quassel::buildInfo().buildDate;
573 reply["CoreStartTime"] = startTime(); // v10 clients don't necessarily parse this, see below
575 // FIXME: newer clients no longer use the hardcoded CoreInfo (for now), since it gets the
576 // time zone wrong. With the next protocol bump (10 -> 11), we should remove this
577 // or make it properly configurable.
579 int uptime = startTime().secsTo(QDateTime::currentDateTime().toUTC());
580 int updays = uptime / 86400; uptime %= 86400;
581 int uphours = uptime / 3600; uptime %= 3600;
582 int upmins = uptime / 60;
583 reply["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
585 "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
586 .arg(Quassel::buildInfo().buildDate)
587 .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(startTime().toString(Qt::TextDate));
589 reply["CoreFeatures"] = (int)Quassel::features();
592 SslServer *sslServer = qobject_cast<SslServer *>(&_server);
593 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(connection->socket());
594 bool supportSsl = sslServer && sslSocket && sslServer->isCertValid();
596 bool supportSsl = false;
599 #ifndef QT_NO_COMPRESS
600 bool supportsCompression = true;
602 bool supportsCompression = false;
605 reply["SupportSsl"] = supportSsl;
606 reply["SupportsCompression"] = supportsCompression;
607 // switch to ssl/compression after client has been informed about our capabilities (see below)
609 reply["LoginEnabled"] = true;
611 // check if we are configured, start wizard otherwise
613 reply["Configured"] = false;
614 QList<QVariant> backends;
615 foreach(Storage *backend, _storageBackends.values()) {
617 v["DisplayName"] = backend->displayName();
618 v["Description"] = backend->description();
619 v["SetupKeys"] = backend->setupKeys();
620 v["SetupDefaults"] = backend->setupDefaults();
623 reply["StorageBackends"] = backends;
624 reply["LoginEnabled"] = false;
627 reply["Configured"] = true;
629 clientInfo[connection] = msg; // store for future reference
630 reply["MsgType"] = "ClientInitAck";
631 connection->writeSocketData(reply);
632 connection->socket()->flush(); // ensure that the write cache is flushed before we switch to ssl
635 // after we told the client that we are ssl capable we switch to ssl mode
636 if (supportSsl && msg["UseSsl"].toBool()) {
637 qDebug() << qPrintable(tr("Starting TLS for Client:")) << connection->description();
638 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors(const QList<QSslError> &)));
639 sslSocket->startServerEncryption();
643 #ifndef QT_NO_COMPRESS
644 if (supportsCompression && msg["UseCompression"].toBool()) {
645 connection->socket()->setProperty("UseCompression", true);
646 qDebug() << "Using compression for Client:" << qPrintable(connection->socket()->peerAddress().toString());
651 // for the rest, we need an initialized connection
652 if (!clientInfo.contains(connection)) {
654 reply["MsgType"] = "ClientLoginReject";
655 reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
656 connection->writeSocketData(reply);
657 qWarning() << qPrintable(tr("Client")) << qPrintable(connection->socket()->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting."));
658 connection->close(); return;
660 if (msg["MsgType"] == "CoreSetupData") {
662 QString result = setupCore(msg["SetupData"].toMap());
663 if (!result.isEmpty()) {
664 reply["MsgType"] = "CoreSetupReject";
665 reply["Error"] = result;
668 reply["MsgType"] = "CoreSetupAck";
670 connection->writeSocketData(reply);
672 else if (msg["MsgType"] == "ClientLogin") {
674 UserId uid = _storage->validateUser(msg["User"].toString(), msg["Password"].toString());
676 reply["MsgType"] = "ClientLoginReject";
677 reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
678 connection->writeSocketData(reply);
681 reply["MsgType"] = "ClientLoginAck";
682 connection->writeSocketData(reply);
683 quInfo() << qPrintable(tr("Client")) << qPrintable(connection->socket()->peerAddress().toString()) << qPrintable(tr("initialized and authenticated successfully as \"%1\" (UserId: %2).").arg(msg["User"].toString()).arg(uid.toInt()));
684 setupClientSession(connection, uid);
690 // Potentially called during the initialization phase (before handing the connection off to the session)
691 void Core::clientDisconnected()
693 RemoteConnection *connection = qobject_cast<RemoteConnection *>(sender());
694 Q_ASSERT(connection);
696 quInfo() << qPrintable(tr("Non-authed client disconnected.")) << qPrintable(connection->socket()->peerAddress().toString());
697 clientInfo.remove(connection);
698 connection->deleteLater();
700 // make server listen again if still not configured
705 // TODO remove unneeded sessions - if necessary/possible...
706 // Suggestion: kill sessions if they are not connected to any network and client.
710 void Core::setupClientSession(RemoteConnection *connection, UserId uid)
712 // From now on everything is handled by the client session
713 disconnect(connection, 0, this, 0);
714 connection->socket()->flush();
715 clientInfo.remove(connection);
717 // Find or create session for validated user
718 SessionThread *session;
719 if (sessions.contains(uid)) {
720 session = sessions[uid];
723 session = createSession(uid);
725 qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(connection->socket()->peerAddress().toString());
731 // as we are currently handling an event triggered by incoming data on this socket
732 // it is unsafe to directly move the socket to the client thread.
733 QCoreApplication::postEvent(this, new AddClientEvent(connection, uid));
737 void Core::customEvent(QEvent *event)
739 if (event->type() == AddClientEventId) {
740 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
741 addClientHelper(addClientEvent->connection, addClientEvent->userId);
747 void Core::addClientHelper(RemoteConnection *connection, UserId uid)
749 // Find or create session for validated user
750 if (!sessions.contains(uid)) {
751 qWarning() << qPrintable(tr("Could not find a session for client:")) << qPrintable(connection->socket()->peerAddress().toString());
756 SessionThread *session = sessions[uid];
757 session->addClient(connection);
761 void Core::setupInternalClientSession(InternalConnection *clientConnection)
765 setupCoreForInternalUsage();
770 uid = _storage->internalUser();
773 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
777 InternalConnection *coreConnection = new InternalConnection(this);
778 coreConnection->setPeer(clientConnection);
779 clientConnection->setPeer(coreConnection);
781 // Find or create session for validated user
782 SessionThread *sessionThread;
783 if (sessions.contains(uid))
784 sessionThread = sessions[uid];
786 sessionThread = createSession(uid);
788 sessionThread->addClient(coreConnection);
792 SessionThread *Core::createSession(UserId uid, bool restore)
794 if (sessions.contains(uid)) {
795 qWarning() << "Calling createSession() when a session for the user already exists!";
798 SessionThread *sess = new SessionThread(uid, restore, this);
799 sessions[uid] = sess;
806 void Core::sslErrors(const QList<QSslError> &errors)
809 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
811 socket->ignoreSslErrors();
817 void Core::socketError(QAbstractSocket::SocketError err)
819 RemoteConnection *connection = qobject_cast<RemoteConnection *>(sender());
820 if (connection && err != QAbstractSocket::RemoteHostClosedError)
821 qWarning() << "Core::socketError()" << connection->socket() << err << connection->socket()->errorString();
825 // migration / backend selection
826 bool Core::selectBackend(const QString &backend)
828 // reregister all storage backends
829 registerStorageBackends();
830 if (!_storageBackends.contains(backend)) {
831 qWarning() << qPrintable(QString("Core::selectBackend(): unsupported backend: %1").arg(backend));
832 qWarning() << " supported backends are:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
836 Storage *storage = _storageBackends[backend];
837 QVariantMap settings = promptForSettings(storage);
839 Storage::State storageState = storage->init(settings);
840 switch (storageState) {
841 case Storage::IsReady:
842 saveBackendSettings(backend, settings);
843 qWarning() << "Switched backend to:" << qPrintable(backend);
844 qWarning() << "Backend already initialized. Skipping Migration";
846 case Storage::NotAvailable:
847 qCritical() << "Backend is not available:" << qPrintable(backend);
849 case Storage::NeedsSetup:
850 if (!storage->setup(settings)) {
851 qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
855 if (storage->init(settings) != Storage::IsReady) {
856 qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
860 saveBackendSettings(backend, settings);
861 qWarning() << "Switched backend to:" << qPrintable(backend);
865 // let's see if we have a current storage object we can migrate from
866 AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
867 AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
868 if (reader && writer) {
869 qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
874 if (reader->migrateTo(writer)) {
875 qDebug() << "Migration finished!";
876 saveBackendSettings(backend, settings);
880 qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
883 // inform the user why we cannot merge
885 qWarning() << "No currently active backend. Skipping migration.";
888 qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
891 qWarning() << "New backend does not support migration:" << qPrintable(backend);
894 // so we were unable to merge, but let's create a user \o/
901 void Core::createUser()
903 QTextStream out(stdout);
904 QTextStream in(stdin);
905 out << "Add a new user:" << endl;
908 QString username = in.readLine().trimmed();
913 QString password = in.readLine().trimmed();
915 out << "Repeat Password: ";
917 QString password2 = in.readLine().trimmed();
921 if (password != password2) {
922 qWarning() << "Passwords don't match!";
925 if (password.isEmpty()) {
926 qWarning() << "Password is empty!";
930 if (_configured && _storage->addUser(username, password).isValid()) {
931 out << "Added user " << username << " successfully!" << endl;
934 qWarning() << "Unable to add user:" << qPrintable(username);
939 void Core::changeUserPass(const QString &username)
941 QTextStream out(stdout);
942 QTextStream in(stdin);
943 UserId userId = _storage->getUserId(username);
944 if (!userId.isValid()) {
945 out << "User " << username << " does not exist." << endl;
949 out << "Change password for user: " << username << endl;
952 out << "New Password: ";
954 QString password = in.readLine().trimmed();
956 out << "Repeat Password: ";
958 QString password2 = in.readLine().trimmed();
962 if (password != password2) {
963 qWarning() << "Passwords don't match!";
966 if (password.isEmpty()) {
967 qWarning() << "Password is empty!";
971 if (_configured && _storage->updateUser(userId, password)) {
972 out << "Password changed successfully!" << endl;
975 qWarning() << "Failed to change password!";
980 AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
985 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
987 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
991 return sqlStorage->createMigrationReader();
995 AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
1000 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1002 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1006 return sqlStorage->createMigrationWriter();
1010 void Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1012 QVariantMap dbsettings;
1013 dbsettings["Backend"] = backend;
1014 dbsettings["ConnectionProperties"] = settings;
1015 CoreSettings().setStorageSettings(dbsettings);
1019 QVariantMap Core::promptForSettings(const Storage *storage)
1021 QVariantMap settings;
1023 QStringList keys = storage->setupKeys();
1027 QTextStream out(stdout);
1028 QTextStream in(stdin);
1029 out << "Default values are in brackets" << endl;
1031 QVariantMap defaults = storage->setupDefaults();
1033 foreach(QString key, keys) {
1035 if (defaults.contains(key)) {
1036 val = defaults[key];
1039 if (!val.toString().isEmpty()) {
1040 out << " (" << val.toString() << ")";
1045 bool noEcho = QString("password").toLower().startsWith(key.toLower());
1049 value = in.readLine().trimmed();
1055 if (!value.isEmpty()) {
1056 switch (defaults[key].type()) {
1058 val = QVariant(value.toInt());
1061 val = QVariant(value);
1064 settings[key] = val;
1071 void Core::stdInEcho(bool on)
1073 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1075 GetConsoleMode(hStdin, &mode);
1077 mode |= ENABLE_ECHO_INPUT;
1079 mode &= ~ENABLE_ECHO_INPUT;
1080 SetConsoleMode(hStdin, mode);
1085 void Core::stdInEcho(bool on)
1088 tcgetattr(STDIN_FILENO, &t);
1093 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1097 #endif /* Q_OS_WIN32 */