cliParser->addOption("loglevel <level>", 'L', "Loglevel Debug|Info|Warning|Error", "Info");
cliParser->addOption("configdir <path>", 'c', "Specify the directory holding configuration files, the SQlite database and the SSL Cert");
cliParser->addOption("datadir <path>", 0, "DEPRECATED - Use --configdir instead");
+ cliParser->addOption("migrate-backend <backendidentifier>", 0, "Starts an interactive session and attempts to migrate your current storage backend to the new one");
+ cliParser->addOption("switch-backend <backendidentifier>", 0, "Starts an interactive session and switches your current storage backend to the new one. No migration will be done!");
#endif
#ifdef HAVE_KDE
SELECT messageid, time, bufferid, type, flags, senderid, message
FROM backlog
-
+WHERE messageid > ? AND messageid <= ?
+ORDER BY messageid ASC
SELECT senderid, sender
FROM sender
+WHERE senderid > ? AND senderid <= ?
+ORDER BY senderid ASC
+
#include <QSqlError>
#include <QSqlQuery>
+int AbstractSqlStorage::_nextConnectionId = 0;
AbstractSqlStorage::AbstractSqlStorage(QObject *parent)
: Storage(parent),
- _schemaVersion(0),
- _nextConnectionId(0)
+ _schemaVersion(0)
{
}
// disconnect the connections, so their deletion is no longer interessting for us
QHash<QThread *, Connection *>::iterator conIter;
for(conIter = _connectionPool.begin(); conIter != _connectionPool.end(); conIter++) {
+ QSqlDatabase::removeDatabase(conIter.value()->name());
disconnect(conIter.value(), 0, this, 0);
}
}
bool AbstractSqlMigrationReader::migrateTo(AbstractSqlMigrationWriter *writer) {
if(!transaction()) {
- qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start reader stransaction!";
+ qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start reader's transaction!";
return false;
}
if(!writer->transaction()) {
- qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start writer stransaction!";
+ qWarning() << "AbstractSqlMigrationReader::migrateTo(): unable to start writer's transaction!";
rollback(); // close the reader transaction;
return false;
}
if(!transferMo(QuasselUser, quasselUserMo))
return false;
- SenderMO senderMo;
- if(!transferMo(Sender, senderMo))
- return false;
-
IdentityMO identityMo;
if(!transferMo(Identity, identityMo))
return false;
if(!transferMo(Buffer, bufferMo))
return false;
+ SenderMO senderMo;
+ if(!transferMo(Sender, senderMo))
+ return false;
+
BacklogMO backlogMo;
if(!transferMo(Backlog, backlogMo))
return false;
int i = 0;
QFile file;
file.open(stdout, QIODevice::WriteOnly);
+
while(readMo(mo)) {
if(!_writer->writeMo(mo)) {
abortMigration(QString("AbstractSqlMigrationReader::transferMo(): unable to transfer Migratable Object of type %1!").arg(AbstractSqlMigrator::migrationObject(moType)));
+ rollback();
+ _writer->rollback();
return false;
}
i++;
file.write("\n");
file.flush();
}
+
qDebug() << "Done.";
return true;
}
int _schemaVersion;
- int _nextConnectionId;
+ static int _nextConnectionId;
QMutex _connectionPoolMutex;
// we let a Connection Object manage each actual db connection
// those objects reside in the thread the connection belongs to
struct SenderMO {
int senderId;
QString sender;
+ SenderMO() : senderId(0) {}
};
struct IdentityMO {
AbstractSqlMigrationReader();
virtual bool readMo(QuasselUserMO &user) = 0;
- virtual bool readMo(SenderMO &sender) = 0;
virtual bool readMo(IdentityMO &identity) = 0;
virtual bool readMo(IdentityNickMO &identityNick) = 0;
virtual bool readMo(NetworkMO &network) = 0;
virtual bool readMo(BufferMO &buffer) = 0;
+ virtual bool readMo(SenderMO &sender) = 0;
virtual bool readMo(BacklogMO &backlog) = 0;
virtual bool readMo(IrcServerMO &ircserver) = 0;
virtual bool readMo(UserSettingMO &userSetting) = 0;
class AbstractSqlMigrationWriter : public AbstractSqlMigrator {
public:
virtual bool writeMo(const QuasselUserMO &user) = 0;
- virtual bool writeMo(const SenderMO &sender) = 0;
virtual bool writeMo(const IdentityMO &identity) = 0;
virtual bool writeMo(const IdentityNickMO &identityNick) = 0;
virtual bool writeMo(const NetworkMO &network) = 0;
virtual bool writeMo(const BufferMO &buffer) = 0;
+ virtual bool writeMo(const SenderMO &sender) = 0;
virtual bool writeMo(const BacklogMO &backlog) = 0;
virtual bool writeMo(const IrcServerMO &ircserver) = 0;
virtual bool writeMo(const UserSettingMO &userSetting) = 0;
exit(EXIT_FAILURE);
}
- // Register storage backends here!
- registerStorageBackend(new SqliteStorage(this));
- registerStorageBackend(new PostgreSqlStorage(this));
+ registerStorageBackends();
connect(&_storageSyncTimer, SIGNAL(timeout()), this, SLOT(syncStorage()));
_storageSyncTimer.start(10 * 60 * 1000); // 10 minutes
}
void Core::init() {
- CoreSettings cs2;
- QVariantMap connectionProperties = cs2.storageSettings().toMap()["ConnectionProperties"].toMap();
- qDebug() << connectionProperties;
- SqliteMigrationReader *reader = new SqliteMigrationReader();
- qDebug() << "reader:" << reader->init();
- PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
- qDebug() << "writer:" << writer->init(connectionProperties);
- qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(reader->displayName(), writer->displayName()));
- if(reader->migrateTo(writer))
- qDebug() << "Migration finished!";
- return;
-
-
-
-
-
-
-
-
-
+ if(Quassel::isOptionSet("switch-backend")) {
+ switchBackend(Quassel::optionValue("switch-backend"));
+ exit(0);
+ }
-
CoreSettings cs;
_configured = initStorage(cs.storageSettings().toMap());
qWarning() << "Core is currently not configured! Please connect with a Quassel Client for basic setup.";
}
+ if(Quassel::isOptionSet("migrate-backend")) {
+ migrateBackend(Quassel::optionValue("migrate-backend"));
+ exit(0);
+ }
+
connect(&_server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
connect(&_v6server, SIGNAL(newConnection()), this, SLOT(incomingConnection()));
if(!startListening()) exit(1); // TODO make this less brutal
}
/*** Storage Handling ***/
+void Core::registerStorageBackends() {
+ // Register storage backends here!
+ registerStorageBackend(new SqliteStorage(this));
+ registerStorageBackend(new PostgreSqlStorage(this));
+}
bool Core::registerStorageBackend(Storage *backend) {
if(backend->isAvailable()) {
}
}
+void Core::unregisterStorageBackends() {
+ foreach(Storage *s, _storageBackends.values()) {
+ s->deleteLater();
+ }
+ _storageBackends.clear();
+}
+
void Core::unregisterStorageBackend(Storage *backend) {
_storageBackends.remove(backend->displayName());
backend->deleteLater();
// old db settings:
// "Type" => "sqlite"
-bool Core::initStorage(QVariantMap dbSettings, bool setup) {
+bool Core::initStorage(const QString &backend, QVariantMap settings, bool setup) {
_storage = 0;
- QString backend = dbSettings["Backend"].toString();
if(backend.isEmpty()) {
return false;
}
return false;
}
- QVariantMap connectionProperties = dbSettings["ConnectionProperties"].toMap();
-
- Storage::State storageState = storage->init(connectionProperties);
+ Storage::State storageState = storage->init(settings);
switch(storageState) {
case Storage::NeedsSetup:
if(!setup)
return false; // trigger setup process
- if(storage->setup(connectionProperties))
- return initStorage(dbSettings, false);
+ if(storage->setup(settings))
+ return initStorage(backend, settings, false);
// if setup wasn't successfull we mark the backend as unavailable
case Storage::NotAvailable:
qCritical() << "Selected storage backend is not available:" << backend;
return false;
case Storage::IsReady:
// delete all other backends
- foreach(Storage *s, _storageBackends.values()) {
- if(s != storage) s->deleteLater();
- }
- _storageBackends.clear();
+ _storageBackends.remove(backend);
+ unregisterStorageBackends();
connect(storage, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)), this, SIGNAL(bufferInfoUpdated(UserId, const BufferInfo &)));
}
_storage = storage;
return true;
}
+bool Core::initStorage(QVariantMap dbSettings, bool setup) {
+ return initStorage(dbSettings["Backend"].toString(), dbSettings["ConnectionProperties"].toMap(), setup);
+}
+
+
void Core::syncStorage() {
if(_storage)
_storage->sync();
if(socket && err != QAbstractSocket::RemoteHostClosedError)
qWarning() << "Core::socketError()" << socket << err << socket->errorString();
}
+
+bool Core::migrateBackend(const QString &backend) {
+ AbstractSqlStorage *sqlStorage = qobject_cast<AbstractSqlStorage *>(_storage);
+ if(!sqlStorage) {
+ qWarning() << "Core::migrateDb(): only SQL based backends can be migrated!";
+ return false;
+ }
+
+ AbstractSqlMigrationReader *reader = sqlStorage->createMigrationReader();
+ if(!reader) {
+ qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration reader for %1)").arg(sqlStorage->displayName()));
+ return false;
+ }
+
+
+ // reregister all storage backends
+ registerStorageBackends();
+ if(_storageBackends.contains(sqlStorage->displayName())) {
+ unregisterStorageBackend(_storageBackends[sqlStorage->displayName()]);
+ }
+ if(!_storageBackends.contains(backend)) {
+ qWarning() << qPrintable(QString("Core::migrateBackend(): unsupported migration target: %1").arg(backend));
+ qWarning() << " supported Targets:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
+ return false;
+ }
+
+ Storage *storage = _storageBackends[backend];
+ QVariantMap settings = promptForSettings(storage->setupKeys());
+
+ Storage::State storageState = storage->init(settings);
+ switch(storageState) {
+ case Storage::NotAvailable:
+ qCritical() << "Core::migrateBackend(): selected storage backend is not available:" << backend;
+ return false;
+ case Storage::NeedsSetup:
+ if(!storage->setup(settings)) {
+ qWarning() << qPrintable(QString("Core::migrateBackend(): unable to setup target: %1").arg(backend));
+ return false;
+ }
+
+ if(storage->init(settings) != Storage::IsReady) {
+ qWarning() << qPrintable(QString("Core::migrateBackend(): unable to initialize target: %1").arg(backend));
+ return false;
+ }
+ case Storage::IsReady:
+ break;
+ }
+
+ sqlStorage = qobject_cast<AbstractSqlStorage *>(storage);
+ if(!sqlStorage) {
+ qWarning() << "Core::migrateDb(): only SQL based backends can be migrated!";
+ return false;
+ }
+ AbstractSqlMigrationWriter *writer = sqlStorage->createMigrationWriter();
+ if(!writer) {
+ qWarning() << qPrintable(QString("Core::migrateDb(): unable to migrate storage backend! (No migration writer for %1)").arg(backend));
+ return false;
+ }
+
+ qDebug() << qPrintable(QString("Migrating Storage backend %1 to %2...").arg(_storage->displayName(), storage->displayName()));
+ delete _storage;
+ _storage = 0;
+ delete storage;
+ storage = 0;
+ if(reader->migrateTo(writer)) {
+ qDebug() << "Migration finished!";
+ saveBackendSettings(backend, settings);
+ return true;
+ }
+ return false;
+}
+
+bool Core::switchBackend(const QString &backend) {
+ if(!_storageBackends.contains(backend)) {
+ qWarning() << qPrintable(QString("Core::switchBackend(): unsupported backend: %1").arg(backend));
+ qWarning() << " supported backends:" << qPrintable(QStringList(_storageBackends.keys()).join(", "));
+ return false;
+ }
+
+ Storage *storage = _storageBackends[backend];
+ QVariantMap settings = promptForSettings(storage->setupKeys());
+
+ bool ok = initStorage(backend, settings, true /* initial setup is allowed */);
+ if(ok) {
+ saveBackendSettings(backend, settings);
+ qWarning() << "Switched backend to:" << qPrintable(backend);
+ } else {
+ qWarning() << "Failed to switch backend to:" << qPrintable(backend);
+ }
+ return ok;
+}
+
+void Core::saveBackendSettings(const QString &backend, const QVariantMap &settings) {
+ QVariantMap dbsettings;
+ dbsettings["Backend"] = backend;
+ dbsettings["ConnectionProperties"] = settings;
+ CoreSettings().setStorageSettings(dbsettings);
+}
+
+QVariantMap Core::promptForSettings(const QVariantMap &map) {
+ QVariantMap settings;
+ if(map.isEmpty())
+ return settings;
+
+ QTextStream out(stdout);
+ QTextStream in(stdin);
+ out << "!!!Warning: echo mode is always on even if asked for a password!!!" << endl;
+
+ QVariantMap::const_iterator iter;
+ QString value;
+ for(iter = map.constBegin(); iter != map.constEnd(); iter++) {
+ out << iter.key() << " (" << iter.value().toString() << "): ";
+ out.flush();
+ value = in.readLine();
+
+ if(value.isEmpty()) {
+ settings[iter.key()] = iter.value();
+ } else {
+ value = value.trimmed();
+ QVariant val;
+ switch(iter.value().type()) {
+ case QVariant::Int:
+ val = QVariant(value.toInt());
+ break;
+ default:
+ val = QVariant(value);
+ }
+ settings[iter.key()] = val;
+ }
+ }
+ return settings;
+}
void clientHasData();
void clientDisconnected();
+ bool initStorage(const QString &backend, QVariantMap settings, bool setup = false);
bool initStorage(QVariantMap dbSettings, bool setup = false);
#ifdef HAVE_SSL
QString setupCoreForInternalUsage();
QString setupCore(QVariantMap setupData);
+ void registerStorageBackends();
bool registerStorageBackend(Storage *);
+ void unregisterStorageBackends();
void unregisterStorageBackend(Storage *);
+ bool migrateBackend(const QString &backend);
+ bool switchBackend(const QString &backend);
+ void saveBackendSettings(const QString &backend, const QVariantMap &settings);
+ QVariantMap promptForSettings(const QVariantMap &map);
QHash<UserId, SessionThread *> sessions;
Storage *_storage;
}
void CoreSession::processMessages() {
- qDebug() << "processing" << _messageQueue.count() << "messages..";
if(_messageQueue.count() == 1) {
const RawMessage &rawMsg = _messageQueue.first();
BufferInfo bufferInfo = Core::bufferInfo(user(), rawMsg.networkId, rawMsg.bufferType, rawMsg.target);
PostgreSqlStorage::~PostgreSqlStorage() {
}
+AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter() {
+ PostgreSqlMigrationWriter *writer = new PostgreSqlMigrationWriter();
+ QVariantMap properties;
+ properties["Username"] = _userName;
+ properties["Password"] = _password;
+ properties["Hostname"] = _hostName;
+ properties["Port"] = _port;
+ properties["Database"] = _databaseName;
+ writer->setConnectionProperties(properties);
+ return writer;
+}
+
bool PostgreSqlStorage::isAvailable() const {
if(!QSqlDatabase::isDriverAvailable("QPSQL")) return false;
return true;
};
};
-inline AbstractSqlMigrationWriter *PostgreSqlStorage::createMigrationWriter() {
- return new PostgreSqlMigrationWriter();
-}
-
-
-
#endif
<file>./SQL/PostgreSQL/14/setup_080_ircservers.sql</file>
<file>./SQL/PostgreSQL/14/setup_090_backlog_idx.sql</file>
<file>./SQL/PostgreSQL/14/setup_100_user_setting.sql</file>
+ <file>./SQL/PostgreSQL/14/setup_110_alter_sender_seq.sql</file>
+ <file>./SQL/PostgreSQL/14/setup_120_alter_messageid_seq.sql</file>
<file>./SQL/PostgreSQL/14/update_backlog_bufferid.sql</file>
<file>./SQL/PostgreSQL/14/update_buffer_lastseen.sql</file>
<file>./SQL/PostgreSQL/14/update_buffer_name.sql</file>
// SqliteMigration
// ========================================
SqliteMigrationReader::SqliteMigrationReader()
- : SqliteStorage()
+ : SqliteStorage(),
+ _maxId(0)
{
}
+void SqliteMigrationReader::setMaxId(MigrationObject mo) {
+ QString queryString;
+ switch(mo) {
+ case Sender:
+ queryString = "SELECT max(senderid) FROM sender";
+ break;
+ case Backlog:
+ queryString = "SELECT max(messageid) FROM backlog";
+ break;
+ default:
+ _maxId = 0;
+ return;
+ }
+ QSqlQuery query = logDb().exec(queryString);
+ query.first();
+ _maxId = query.value(0).toInt();
+}
+
bool SqliteMigrationReader::prepareQuery(MigrationObject mo) {
- QString query;
+ setMaxId(mo);
+
switch(mo) {
case QuasselUser:
- query = queryString("migrate_read_quasseluser");
- break;
- case Sender:
- query = queryString("migrate_read_sender");
+ newQuery(queryString("migrate_read_quasseluser"), logDb());
break;
case Identity:
- query = queryString("migrate_read_identity");
+ newQuery(queryString("migrate_read_identity"), logDb());
break;
case IdentityNick:
- query = queryString("migrate_read_identity_nick");
+ newQuery(queryString("migrate_read_identity_nick"), logDb());
break;
case Network:
- query = queryString("migrate_read_network");
+ newQuery(queryString("migrate_read_network"), logDb());
break;
case Buffer:
- query = queryString("migrate_read_buffer");
+ newQuery(queryString("migrate_read_buffer"), logDb());
+ break;
+ case Sender:
+ newQuery(queryString("migrate_read_sender"), logDb());
+ bindValue(0, 0);
+ bindValue(1, stepSize());
break;
case Backlog:
- query = queryString("migrate_read_backlog");
+ newQuery(queryString("migrate_read_backlog"), logDb());
+ bindValue(0, 0);
+ bindValue(1, stepSize());
break;
case IrcServer:
- query = queryString("migrate_read_ircserver");
+ newQuery(queryString("migrate_read_ircserver"), logDb());
break;
case UserSetting:
- query = queryString("migrate_read_usersetting");
+ newQuery(queryString("migrate_read_usersetting"), logDb());
break;
}
- newQuery(query, logDb());
return exec();
}
-//bool SqliteMigrationReader::readUser(QuasselUserMO &user) {
bool SqliteMigrationReader::readMo(QuasselUserMO &user) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readSender(SenderMO &sender) {
-bool SqliteMigrationReader::readMo(SenderMO &sender) {
- if(!next())
- return false;
-
- sender.senderId = value(0).toInt();
- sender.sender = value(1).toString();
- return true;
-}
-
-//bool SqliteMigrationReader::readIdentity(IdentityMO &identity) {
bool SqliteMigrationReader::readMo(IdentityMO &identity) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readIdentityNick(IdentityNickMO &identityNick) {
bool SqliteMigrationReader::readMo(IdentityNickMO &identityNick) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readNetwork(NetworkMO &network) {
bool SqliteMigrationReader::readMo(NetworkMO &network) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readBuffer(BufferMO &buffer) {
bool SqliteMigrationReader::readMo(BufferMO &buffer) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readBacklog(BacklogMO &backlog) {
+bool SqliteMigrationReader::readMo(SenderMO &sender) {
+ int skipSteps = 0;
+ while(!next()) {
+ if(sender.senderId < _maxId) {
+ bindValue(0, sender.senderId + (skipSteps * stepSize()));
+ bindValue(1, sender.senderId + ((skipSteps + 1) * stepSize()));
+ skipSteps++;
+ if(!exec())
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ sender.senderId = value(0).toInt();
+ sender.sender = value(1).toString();
+ return true;
+}
+
bool SqliteMigrationReader::readMo(BacklogMO &backlog) {
- if(!next())
- return false;
+ int skipSteps = 0;
+ while(!next()) {
+ if(backlog.messageid < _maxId) {
+ bindValue(0, backlog.messageid.toInt() + (skipSteps * stepSize()));
+ bindValue(1, backlog.messageid.toInt() + ((skipSteps + 1) * stepSize()));
+ skipSteps++;
+ if(!exec())
+ return false;
+ } else {
+ return false;
+ }
+ }
backlog.messageid = value(0).toInt();
backlog.time = QDateTime::fromTime_t(value(1).toInt());
return true;
}
-//bool SqliteMigrationReader::readIrcServer(IrcServerMO &ircserver) {
bool SqliteMigrationReader::readMo(IrcServerMO &ircserver) {
if(!next())
return false;
return true;
}
-//bool SqliteMigrationReader::readUserSetting(UserSettingMO &userSetting) {
bool SqliteMigrationReader::readMo(UserSettingMO &userSetting) {
if(!next())
return false;
return true;
}
-
-
virtual bool prepareQuery(MigrationObject mo);
+ inline int stepSize() { return 50000; }
+
protected:
virtual inline bool transaction() { return logDb().transaction(); }
virtual inline void rollback() { logDb().rollback(); }
virtual inline bool commit() { return logDb().commit(); }
+
+private:
+ void setMaxId(MigrationObject mo);
+ int _maxId;
};
inline AbstractSqlMigrationReader *SqliteStorage::createMigrationReader() {