1 /***************************************************************************
2 * Copyright (C) 2005-2013 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 "internalpeer.h"
27 #include "postgresqlstorage.h"
29 #include "sqlitestorage.h"
35 #include "protocols/legacy/legacypeer.h"
44 #endif /* Q_OS_WIN32 */
47 # include <sys/types.h>
48 # include <sys/stat.h>
49 #endif /* HAVE_UMASK */
51 // ==============================
53 // ==============================
54 const int Core::AddClientEventId = QEvent::registerEventType();
56 class AddClientEvent : public QEvent
59 AddClientEvent(RemotePeer *p, UserId uid) : QEvent(QEvent::Type(Core::AddClientEventId)), peer(p), userId(uid) {}
65 // ==============================
67 // ==============================
68 Core *Core::instanceptr = 0;
70 Core *Core::instance()
72 if (instanceptr) return instanceptr;
73 instanceptr = new Core();
90 umask(S_IRWXG | S_IRWXO);
92 _startTime = QDateTime::currentDateTime().toUTC(); // for uptime :)
94 Quassel::loadTranslation(QLocale::system());
96 // FIXME: MIGRATION 0.3 -> 0.4: Move database and core config to new location
97 // Move settings, note this does not delete the old files
99 QSettings newSettings("quassel-irc.org", "quasselcore");
103 QSettings::Format format = QSettings::IniFormat;
105 QSettings::Format format = QSettings::NativeFormat;
107 QString newFilePath = Quassel::configDirPath() + "quasselcore"
108 + ((format == QSettings::NativeFormat) ? QLatin1String(".conf") : QLatin1String(".ini"));
109 QSettings newSettings(newFilePath, format);
110 #endif /* Q_WS_MAC */
112 if (newSettings.value("Config/Version").toUInt() == 0) {
114 QString org = "quassel-irc.org";
116 QString org = "Quassel Project";
118 QSettings oldSettings(org, "Quassel Core");
119 if (oldSettings.allKeys().count()) {
120 qWarning() << "\n\n*** IMPORTANT: Config and data file locations have changed. Attempting to auto-migrate your core settings...";
121 foreach(QString key, oldSettings.allKeys())
122 newSettings.setValue(key, oldSettings.value(key));
123 newSettings.setValue("Config/Version", 1);
124 qWarning() << "* Your core settings have been migrated to" << newSettings.fileName();
126 #ifndef Q_WS_MAC /* we don't need to move the db and cert for mac */
128 QString quasselDir = qgetenv("APPDATA") + "/quassel/";
129 #elif defined Q_WS_MAC
130 QString quasselDir = QDir::homePath() + "/Library/Application Support/Quassel/";
132 QString quasselDir = QDir::homePath() + "/.quassel/";
135 QFileInfo info(Quassel::configDirPath() + "quassel-storage.sqlite");
136 if (!info.exists()) {
137 // move database, if we found it
138 QFile oldDb(quasselDir + "quassel-storage.sqlite");
139 if (oldDb.exists()) {
140 bool success = oldDb.rename(Quassel::configDirPath() + "quassel-storage.sqlite");
142 qWarning() << "* Your database has been moved to" << Quassel::configDirPath() + "quassel-storage.sqlite";
144 qWarning() << "!!! Moving your database has failed. Please move it manually into" << Quassel::configDirPath();
148 QFileInfo certInfo(quasselDir + "quasselCert.pem");
149 if (certInfo.exists()) {
150 QFile cert(quasselDir + "quasselCert.pem");
151 bool success = cert.rename(Quassel::configDirPath() + "quasselCert.pem");
153 qWarning() << "* Your certificate has been moved to" << Quassel::configDirPath() + "quasselCert.pem";
155 qWarning() << "!!! Moving your certificate has failed. Please move it manually into" << Quassel::configDirPath();
157 #endif /* !Q_WS_MAC */
158 qWarning() << "*** Migration completed.\n\n";
163 // check settings version
164 // so far, we only have 1
166 if (s.version() != 1) {
167 qCritical() << "Invalid core settings version, terminating!";
171 registerStorageBackends();
173 connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
174 _storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
181 _configured = initStorage(cs.storageSettings().toMap());
183 if (Quassel::isOptionSet("select-backend")) {
184 selectBackend(Quassel::optionValue("select-backend"));
189 if (!_storageBackends.count()) {
190 qWarning() << qPrintable(tr("Could not initialize any storage backend! Exiting..."));
191 qWarning() << qPrintable(tr("Currently, Quassel supports SQLite3 and PostgreSQL. You need to build your\n"
192 "Qt library with the sqlite or postgres plugin enabled in order for quasselcore\n"
194 exit(1); // TODO make this less brutal (especially for mono client -> popup)
196 qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
199 if (Quassel::isOptionSet("add-user")) {
204 if (Quassel::isOptionSet("change-userpass")) {
205 changeUserPass(Quassel::optionValue("change-userpass"));
209 connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
210 connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
211 if (!startListening()) exit(1); // TODO make this less brutal
213 if (Quassel::isOptionSet("oidentd"))
214 _oidentdConfigGenerator = new OidentdConfigGenerator(this);
220 foreach(RemotePeer *peer, clientInfo.keys()) {
221 peer->close(); // disconnect non authed clients
223 qDeleteAll(sessions);
224 qDeleteAll(_storageBackends);
228 /*** Session Restore ***/
230 void Core::saveState()
234 QVariantList activeSessions;
235 foreach(UserId user, instance()->sessions.keys()) activeSessions << QVariant::fromValue<UserId>(user);
236 state["CoreStateVersion"] = 1;
237 state["ActiveSessions"] = activeSessions;
238 s.setCoreState(state);
242 void Core::restoreState()
244 if (!instance()->_configured) {
245 // qWarning() << qPrintable(tr("Cannot restore a state for an unconfigured core!"));
248 if (instance()->sessions.count()) {
249 qWarning() << qPrintable(tr("Calling restoreState() even though active sessions exist!"));
253 /* We don't check, since we are at the first version since switching to Git
254 uint statever = s.coreState().toMap()["CoreStateVersion"].toUInt();
256 qWarning() << qPrintable(tr("Core state too old, ignoring..."));
261 QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
262 if (activeSessions.count() > 0) {
263 quInfo() << "Restoring previous core state...";
264 foreach(QVariant v, activeSessions) {
265 UserId user = v.value<UserId>();
266 instance()->createSession(user, true);
273 QString Core::setupCoreForInternalUsage()
275 Q_ASSERT(!_storageBackends.isEmpty());
276 QVariantMap setupData;
277 qsrand(QDateTime::currentDateTime().toTime_t());
279 for (int i = 0; i < 10; i++) {
281 pass += qrand() % 10;
283 setupData["AdminUser"] = "AdminUser";
284 setupData["AdminPasswd"] = QString::number(pass);
285 setupData["Backend"] = QString("SQLite"); // mono client currently needs sqlite
286 return setupCore(setupData);
290 QString Core::setupCore(QVariantMap setupData)
292 QString user = setupData.take("AdminUser").toString();
293 QString password = setupData.take("AdminPasswd").toString();
294 if (user.isEmpty() || password.isEmpty()) {
295 return tr("Admin user or password not set.");
297 if (_configured || !(_configured = initStorage(setupData, true))) {
298 return tr("Could not setup storage!");
301 s.setStorageSettings(setupData);
302 quInfo() << qPrintable(tr("Creating admin user..."));
303 _storage->addUser(user, password);
304 startListening(); // TODO check when we need this
309 /*** Storage Handling ***/
310 void Core::registerStorageBackends()
312 // Register storage backends here!
313 registerStorageBackend(new SqliteStorage(this));
314 registerStorageBackend(new PostgreSqlStorage(this));
318 bool Core::registerStorageBackend(Storage *backend)
320 if (backend->isAvailable()) {
321 _storageBackends[backend->displayName()] = backend;
325 backend->deleteLater();
331 void Core::unregisterStorageBackends()
333 foreach(Storage *s, _storageBackends.values()) {
336 _storageBackends.clear();
340 void Core::unregisterStorageBackend(Storage *backend)
342 _storageBackends.remove(backend->displayName());
343 backend->deleteLater();
348 // "Type" => "sqlite"
349 bool Core::initStorage(const QString &backend, QVariantMap settings, bool setup)
353 if (backend.isEmpty()) {
357 Storage *storage = 0;
358 if (_storageBackends.contains(backend)) {
359 storage = _storageBackends[backend];
362 qCritical() << "Selected storage backend is not available:" << backend;
366 Storage::State storageState = storage->init(settings);
367 switch (storageState) {
368 case Storage::NeedsSetup:
370 return false; // trigger setup process
371 if (storage->setup(settings))
372 return initStorage(backend, settings, false);
373 // if setup wasn't successfull we mark the backend as unavailable
374 case Storage::NotAvailable:
375 qCritical() << "Selected storage backend is not available:" << backend;
376 storage->deleteLater();
377 _storageBackends.remove(backend);
380 case Storage::IsReady:
381 // delete all other backends
382 _storageBackends.remove(backend);
383 unregisterStorageBackends();
384 connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
391 bool Core::initStorage(QVariantMap dbSettings, bool setup)
393 return initStorage(dbSettings["Backend"].toString(), dbSettings["ConnectionProperties"].toMap(), setup);
397 void Core::syncStorage()
404 /*** Storage Access ***/
405 bool Core::createNetwork(UserId user, NetworkInfo &info)
407 NetworkId networkId = instance()->_storage->createNetwork(user, info);
408 if (!networkId.isValid())
411 info.networkId = networkId;
416 /*** Network Management ***/
418 bool Core::startListening()
420 // in mono mode we only start a local port if a port is specified in the cli call
421 if (Quassel::runMode() == Quassel::Monolithic && !Quassel::isOptionSet("port"))
424 bool success = false;
425 uint port = Quassel::optionValue("port").toUInt();
427 const QString listen = Quassel::optionValue("listen");
428 const QStringList listen_list = listen.split(",", QString::SkipEmptyParts);
429 if (listen_list.size() > 0) {
430 foreach(const QString listen_term, listen_list) { // TODO: handle multiple interfaces for same TCP version gracefully
432 if (!addr.setAddress(listen_term)) {
433 qCritical() << qPrintable(
434 tr("Invalid listen address %1")
439 switch (addr.protocol()) {
440 case QAbstractSocket::IPv6Protocol:
441 if (_v6server.listen(addr, port)) {
442 quInfo() << qPrintable(
443 tr("Listening for GUI clients on IPv6 %1 port %2 using protocol version %3")
444 .arg(addr.toString())
445 .arg(_v6server.serverPort())
446 .arg(Quassel::buildInfo().protocolVersion)
451 quWarning() << qPrintable(
452 tr("Could not open IPv6 interface %1:%2: %3")
453 .arg(addr.toString())
455 .arg(_v6server.errorString()));
457 case QAbstractSocket::IPv4Protocol:
458 if (_server.listen(addr, port)) {
459 quInfo() << qPrintable(
460 tr("Listening for GUI clients on IPv4 %1 port %2 using protocol version %3")
461 .arg(addr.toString())
462 .arg(_server.serverPort())
463 .arg(Quassel::buildInfo().protocolVersion)
468 // if v6 succeeded on Any, the port will be already in use - don't display the error then
469 if (!success || _server.serverError() != QAbstractSocket::AddressInUseError)
470 quWarning() << qPrintable(
471 tr("Could not open IPv4 interface %1:%2: %3")
472 .arg(addr.toString())
474 .arg(_server.errorString()));
478 qCritical() << qPrintable(
479 tr("Invalid listen address %1, unknown network protocol")
488 quError() << qPrintable(tr("Could not open any network interfaces to listen on!"));
494 void Core::stopListening(const QString &reason)
496 bool wasListening = false;
497 if (_server.isListening()) {
501 if (_v6server.isListening()) {
506 if (reason.isEmpty())
507 quInfo() << "No longer listening for GUI clients.";
509 quInfo() << qPrintable(reason);
514 void Core::incomingConnection()
516 QTcpServer *server = qobject_cast<QTcpServer *>(sender());
518 while (server->hasPendingConnections()) {
519 QTcpSocket *socket = server->nextPendingConnection();
520 RemotePeer *peer = new LegacyPeer(socket, this);
522 connect(peer, SIGNAL(disconnected()), SLOT(clientDisconnected()));
523 connect(peer, SIGNAL(dataReceived(QVariant)), SLOT(processClientMessage(QVariant)));
524 connect(peer, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError)));
526 clientInfo.insert(peer, QVariantMap());
527 quInfo() << qPrintable(tr("Client connected from")) << qPrintable(socket->peerAddress().toString());
530 stopListening(tr("Closing server for basic setup."));
536 void Core::processClientMessage(const QVariant &data)
538 RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
540 qWarning() << Q_FUNC_INFO << "Message not sent by RemoteConnection!";
544 QVariantMap msg = data.toMap();
545 if (!msg.contains("MsgType")) {
546 // Client is way too old, does not even use the current init format
547 qWarning() << qPrintable(tr("Antique client trying to connect... refusing."));
552 // OK, so we have at least an init message format we can understand
553 if (msg["MsgType"] == "ClientInit") {
556 // Just version information -- check it!
557 uint ver = msg["ProtocolVersion"].toUInt();
558 if (ver < Quassel::buildInfo().coreNeedsProtocol) {
559 reply["MsgType"] = "ClientInitReject";
560 reply["Error"] = tr("<b>Your Quassel Client is too old!</b><br>"
561 "This core needs at least client/core protocol version %1.<br>"
562 "Please consider upgrading your client.").arg(Quassel::buildInfo().coreNeedsProtocol);
563 peer->writeSocketData(reply);
564 qWarning() << qPrintable(tr("Client")) << peer->description() << qPrintable(tr("too old, rejecting."));
569 reply["ProtocolVersion"] = Quassel::buildInfo().protocolVersion;
570 reply["CoreVersion"] = Quassel::buildInfo().fancyVersionString;
571 reply["CoreDate"] = Quassel::buildInfo().buildDate;
572 reply["CoreStartTime"] = startTime(); // v10 clients don't necessarily parse this, see below
574 // FIXME: newer clients no longer use the hardcoded CoreInfo (for now), since it gets the
575 // time zone wrong. With the next protocol bump (10 -> 11), we should remove this
576 // or make it properly configurable.
578 int uptime = startTime().secsTo(QDateTime::currentDateTime().toUTC());
579 int updays = uptime / 86400; uptime %= 86400;
580 int uphours = uptime / 3600; uptime %= 3600;
581 int upmins = uptime / 60;
582 reply["CoreInfo"] = tr("<b>Quassel Core Version %1</b><br>"
584 "Up %3d%4h%5m (since %6)").arg(Quassel::buildInfo().fancyVersionString)
585 .arg(Quassel::buildInfo().buildDate)
586 .arg(updays).arg(uphours, 2, 10, QChar('0')).arg(upmins, 2, 10, QChar('0')).arg(startTime().toString(Qt::TextDate));
588 reply["CoreFeatures"] = (int)Quassel::features();
591 SslServer *sslServer = qobject_cast<SslServer *>(&_server);
592 QSslSocket *sslSocket = qobject_cast<QSslSocket *>(peer->socket());
593 bool supportSsl = sslServer && sslSocket && sslServer->isCertValid();
595 bool supportSsl = false;
598 #ifndef QT_NO_COMPRESS
599 bool supportsCompression = true;
601 bool supportsCompression = false;
604 reply["SupportSsl"] = supportSsl;
605 reply["SupportsCompression"] = supportsCompression;
606 // switch to ssl/compression after client has been informed about our capabilities (see below)
608 reply["LoginEnabled"] = true;
610 // check if we are configured, start wizard otherwise
612 reply["Configured"] = false;
613 QList<QVariant> backends;
614 foreach(Storage *backend, _storageBackends.values()) {
616 v["DisplayName"] = backend->displayName();
617 v["Description"] = backend->description();
618 v["SetupKeys"] = backend->setupKeys();
619 v["SetupDefaults"] = backend->setupDefaults();
622 reply["StorageBackends"] = backends;
623 reply["LoginEnabled"] = false;
626 reply["Configured"] = true;
628 clientInfo[peer] = msg; // store for future reference
629 reply["MsgType"] = "ClientInitAck";
630 peer->writeSocketData(reply);
631 peer->socket()->flush(); // ensure that the write cache is flushed before we switch to ssl
634 // after we told the client that we are ssl capable we switch to ssl mode
635 if (supportSsl && msg["UseSsl"].toBool()) {
636 qDebug() << qPrintable(tr("Starting TLS for Client:")) << peer->description();
637 connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), SLOT(sslErrors(const QList<QSslError> &)));
638 sslSocket->startServerEncryption();
642 #ifndef QT_NO_COMPRESS
643 if (supportsCompression && msg["UseCompression"].toBool()) {
644 peer->socket()->setProperty("UseCompression", true);
645 qDebug() << "Using compression for Client:" << qPrintable(peer->socket()->peerAddress().toString());
650 // for the rest, we need an initialized connection
651 if (!clientInfo.contains(peer)) {
653 reply["MsgType"] = "ClientLoginReject";
654 reply["Error"] = tr("<b>Client not initialized!</b><br>You need to send an init message before trying to login.");
655 peer->writeSocketData(reply);
656 qWarning() << qPrintable(tr("Client")) << qPrintable(peer->socket()->peerAddress().toString()) << qPrintable(tr("did not send an init message before trying to login, rejecting."));
657 peer->close(); return;
659 if (msg["MsgType"] == "CoreSetupData") {
661 QString result = setupCore(msg["SetupData"].toMap());
662 if (!result.isEmpty()) {
663 reply["MsgType"] = "CoreSetupReject";
664 reply["Error"] = result;
667 reply["MsgType"] = "CoreSetupAck";
669 peer->writeSocketData(reply);
671 else if (msg["MsgType"] == "ClientLogin") {
673 UserId uid = _storage->validateUser(msg["User"].toString(), msg["Password"].toString());
675 reply["MsgType"] = "ClientLoginReject";
676 reply["Error"] = tr("<b>Invalid username or password!</b><br>The username/password combination you supplied could not be found in the database.");
677 peer->writeSocketData(reply);
680 reply["MsgType"] = "ClientLoginAck";
681 peer->writeSocketData(reply);
682 quInfo() << qPrintable(tr("Client")) << qPrintable(peer->socket()->peerAddress().toString()) << qPrintable(tr("initialized and authenticated successfully as \"%1\" (UserId: %2).").arg(msg["User"].toString()).arg(uid.toInt()));
683 setupClientSession(peer, uid);
689 // Potentially called during the initialization phase (before handing the connection off to the session)
690 void Core::clientDisconnected()
692 RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
695 quInfo() << qPrintable(tr("Non-authed client disconnected.")) << qPrintable(peer->socket()->peerAddress().toString());
696 clientInfo.remove(peer);
699 // make server listen again if still not configured
704 // TODO remove unneeded sessions - if necessary/possible...
705 // Suggestion: kill sessions if they are not connected to any network and client.
709 void Core::setupClientSession(RemotePeer *peer, UserId uid)
711 // From now on everything is handled by the client session
712 disconnect(peer, 0, this, 0);
713 peer->socket()->flush();
714 clientInfo.remove(peer);
716 // Find or create session for validated user
717 SessionThread *session;
718 if (sessions.contains(uid)) {
719 session = sessions[uid];
722 session = createSession(uid);
724 qWarning() << qPrintable(tr("Could not initialize session for client:")) << qPrintable(peer->socket()->peerAddress().toString());
730 // as we are currently handling an event triggered by incoming data on this socket
731 // it is unsafe to directly move the socket to the client thread.
732 QCoreApplication::postEvent(this, new AddClientEvent(peer, uid));
736 void Core::customEvent(QEvent *event)
738 if (event->type() == AddClientEventId) {
739 AddClientEvent *addClientEvent = static_cast<AddClientEvent *>(event);
740 addClientHelper(addClientEvent->peer, addClientEvent->userId);
746 void Core::addClientHelper(RemotePeer *peer, UserId uid)
748 // Find or create session for validated user
749 if (!sessions.contains(uid)) {
750 qWarning() << qPrintable(tr("Could not find a session for client:")) << qPrintable(peer->socket()->peerAddress().toString());
755 SessionThread *session = sessions[uid];
756 session->addClient(peer);
760 void Core::setupInternalClientSession(InternalPeer *clientPeer)
764 setupCoreForInternalUsage();
769 uid = _storage->internalUser();
772 qWarning() << "Core::setupInternalClientSession(): You're trying to run monolithic Quassel with an unusable Backend! Go fix it!";
776 InternalPeer *corePeer = new InternalPeer(this);
777 corePeer->setPeer(clientPeer);
778 clientPeer->setPeer(corePeer);
780 // Find or create session for validated user
781 SessionThread *sessionThread;
782 if (sessions.contains(uid))
783 sessionThread = sessions[uid];
785 sessionThread = createSession(uid);
787 sessionThread->addClient(corePeer);
791 SessionThread *Core::createSession(UserId uid, bool restore)
793 if (sessions.contains(uid)) {
794 qWarning() << "Calling createSession() when a session for the user already exists!";
797 SessionThread *sess = new SessionThread(uid, restore, this);
798 sessions[uid] = sess;
805 void Core::sslErrors(const QList<QSslError> &errors)
808 QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
810 socket->ignoreSslErrors();
816 void Core::socketError(QAbstractSocket::SocketError err)
818 RemotePeer *peer = qobject_cast<RemotePeer *>(sender());
819 if (peer && err != QAbstractSocket::RemoteHostClosedError)
820 qWarning() << "Core::socketError()" << peer->socket() << err << peer->socket()->errorString();
824 // migration / backend selection
825 bool Core::selectBackend(const QString &backend)
827 // reregister all storage backends
828 registerStorageBackends();
829 if (!_storageBackends.contains(backend)) {
830 qWarning() << qPrintable(QString("Core::selectBackend(): unsupported backend: %1").arg(backend));
831 qWarning() << " supported backends are:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
835 Storage *storage = _storageBackends[backend];
836 QVariantMap settings = promptForSettings(storage);
838 Storage::State storageState = storage->init(settings);
839 switch (storageState) {
840 case Storage::IsReady:
841 saveBackendSettings(backend, settings);
842 qWarning() << "Switched backend to:" << qPrintable(backend);
843 qWarning() << "Backend already initialized. Skipping Migration";
845 case Storage::NotAvailable:
846 qCritical() << "Backend is not available:" << qPrintable(backend);
848 case Storage::NeedsSetup:
849 if (!storage->setup(settings)) {
850 qWarning() << qPrintable(QString("Core::selectBackend(): unable to setup backend: %1").arg(backend));
854 if (storage->init(settings) != Storage::IsReady) {
855 qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize backend: %1").arg(backend));
859 saveBackendSettings(backend, settings);
860 qWarning() << "Switched backend to:" << qPrintable(backend);
864 // let's see if we have a current storage object we can migrate from
865 AbstractSqlMigrationReader *reader = getMigrationReader(_storage);
866 AbstractSqlMigrationWriter *writer = getMigrationWriter(storage);
867 if (reader && writer) {
868 qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
873 if (reader->migrateTo(writer)) {
874 qDebug() << "Migration finished!";
875 saveBackendSettings(backend, settings);
879 qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
882 // inform the user why we cannot merge
884 qWarning() << "No currently active backend. Skipping migration.";
887 qWarning() << "Currently active backend does not support migration:" << qPrintable(_storage->displayName());
890 qWarning() << "New backend does not support migration:" << qPrintable(backend);
893 // so we were unable to merge, but let's create a user \o/
900 void Core::createUser()
902 QTextStream out(stdout);
903 QTextStream in(stdin);
904 out << "Add a new user:" << endl;
907 QString username = in.readLine().trimmed();
912 QString password = in.readLine().trimmed();
914 out << "Repeat Password: ";
916 QString password2 = in.readLine().trimmed();
920 if (password != password2) {
921 qWarning() << "Passwords don't match!";
924 if (password.isEmpty()) {
925 qWarning() << "Password is empty!";
929 if (_configured && _storage->addUser(username, password).isValid()) {
930 out << "Added user " << username << " successfully!" << endl;
933 qWarning() << "Unable to add user:" << qPrintable(username);
938 void Core::changeUserPass(const QString &username)
940 QTextStream out(stdout);
941 QTextStream in(stdin);
942 UserId userId = _storage->getUserId(username);
943 if (!userId.isValid()) {
944 out << "User " << username << " does not exist." << endl;
948 out << "Change password for user: " << username << endl;
951 out << "New Password: ";
953 QString password = in.readLine().trimmed();
955 out << "Repeat Password: ";
957 QString password2 = in.readLine().trimmed();
961 if (password != password2) {
962 qWarning() << "Passwords don't match!";
965 if (password.isEmpty()) {
966 qWarning() << "Password is empty!";
970 if (_configured && _storage->updateUser(userId, password)) {
971 out << "Password changed successfully!" << endl;
974 qWarning() << "Failed to change password!";
979 AbstractSqlMigrationReader *Core::getMigrationReader(Storage *storage)
984 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
986 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
990 return sqlStorage->createMigrationReader();
994 AbstractSqlMigrationWriter *Core::getMigrationWriter(Storage *storage)
999 AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
1001 qDebug() << "Core::migrateDb(): only SQL based backends can be migrated!";
1005 return sqlStorage->createMigrationWriter();
1009 void Core::saveBackendSettings(const QString &backend, const QVariantMap &settings)
1011 QVariantMap dbsettings;
1012 dbsettings["Backend"] = backend;
1013 dbsettings["ConnectionProperties"] = settings;
1014 CoreSettings().setStorageSettings(dbsettings);
1018 QVariantMap Core::promptForSettings(const Storage *storage)
1020 QVariantMap settings;
1022 QStringList keys = storage->setupKeys();
1026 QTextStream out(stdout);
1027 QTextStream in(stdin);
1028 out << "Default values are in brackets" << endl;
1030 QVariantMap defaults = storage->setupDefaults();
1032 foreach(QString key, keys) {
1034 if (defaults.contains(key)) {
1035 val = defaults[key];
1038 if (!val.toString().isEmpty()) {
1039 out << " (" << val.toString() << ")";
1044 bool noEcho = QString("password").toLower().startsWith(key.toLower());
1048 value = in.readLine().trimmed();
1054 if (!value.isEmpty()) {
1055 switch (defaults[key].type()) {
1057 val = QVariant(value.toInt());
1060 val = QVariant(value);
1063 settings[key] = val;
1070 void Core::stdInEcho(bool on)
1072 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
1074 GetConsoleMode(hStdin, &mode);
1076 mode |= ENABLE_ECHO_INPUT;
1078 mode &= ~ENABLE_ECHO_INPUT;
1079 SetConsoleMode(hStdin, mode);
1084 void Core::stdInEcho(bool on)
1087 tcgetattr(STDIN_FILENO, &t);
1092 tcsetattr(STDIN_FILENO, TCSANOW, &t);
1096 #endif /* Q_OS_WIN32 */