Store core session state in database rather than configuration file.
This improves containerization as the configuration file doesn't need
to be preserved, and also makes database restoration handle restoring
active sessions.
Migrate existing legacy sessions to database storage, ensuring that
the core state fallback is handled correctly, too.
Part of GH-341.
--- /dev/null
+INSERT INTO core_state (key, value)
+VALUES (:key, :value)
--- /dev/null
+INSERT INTO core_state (key, value)
+VALUES (?, ?)
--- /dev/null
+SELECT value
+FROM core_state
+WHERE key = :key
--- /dev/null
+CREATE TABLE core_state (
+ key TEXT NOT NULL,
+ value bytea,
+ PRIMARY KEY (key)
+)
--- /dev/null
+UPDATE core_state
+SET value = :value
+WHERE key = :key
--- /dev/null
+CREATE TABLE core_state (
+ key TEXT NOT NULL,
+ value bytea,
+ PRIMARY KEY (key)
+)
--- /dev/null
+INSERT INTO core_state (key, value)
+VALUES (:key, :value)
--- /dev/null
+SELECT key, value
+FROM core_state
+
--- /dev/null
+SELECT value
+FROM core_state
+WHERE key = :key
--- /dev/null
+CREATE TABLE core_state (
+ key TEXT NOT NULL,
+ value bytea,
+ PRIMARY KEY (key)
+)
--- /dev/null
+UPDATE core_state
+SET value = :value
+WHERE key = :key
--- /dev/null
+CREATE TABLE core_state (
+ key TEXT NOT NULL,
+ value bytea,
+ PRIMARY KEY (key)
+)
return "IrcServer";
case UserSetting:
return "UserSetting";
+ case CoreState:
+ return "CoreState";
};
return QString();
}
if (!transferMo(UserSetting, userSettingMo))
return false;
+ CoreStateMO coreStateMO;
+ if (!transferMo(CoreState, coreStateMO))
+ return false;
+
if (!_writer->postProcess())
abortMigration();
return finalizeMigration();
QByteArray settingvalue;
};
+ struct CoreStateMO {
+ QString key;
+ QByteArray value;
+ };
+
enum MigrationObject {
QuasselUser,
Sender,
Buffer,
Backlog,
IrcServer,
- UserSetting
+ UserSetting,
+ CoreState
};
AbstractSqlMigrator();
virtual bool readMo(BacklogMO &backlog) = 0;
virtual bool readMo(IrcServerMO &ircserver) = 0;
virtual bool readMo(UserSettingMO &userSetting) = 0;
+ virtual bool readMo(CoreStateMO &coreState) = 0;
bool migrateTo(AbstractSqlMigrationWriter *writer);
virtual bool writeMo(const BacklogMO &backlog) = 0;
virtual bool writeMo(const IrcServerMO &ircserver) = 0;
virtual bool writeMo(const UserSettingMO &userSetting) = 0;
+ virtual bool writeMo(const CoreStateMO &coreState) = 0;
inline bool migrateFrom(AbstractSqlMigrationReader *reader) { return reader->migrateTo(this); }
void Core::saveState()
{
- CoreSettings s;
- QVariantMap state;
QVariantList activeSessions;
foreach(UserId user, instance()->_sessions.keys())
activeSessions << QVariant::fromValue<UserId>(user);
- state["CoreStateVersion"] = 1;
- state["ActiveSessions"] = activeSessions;
- s.setCoreState(state);
+ instance()->_storage->setCoreState(activeSessions);
}
}
*/
- QVariantList activeSessions = s.coreState().toMap()["ActiveSessions"].toList();
+ const QList<QVariant> &activeSessionsFallback = s.coreState().toMap()["ActiveSessions"].toList();
+ QVariantList activeSessions = instance()->_storage->getCoreState(activeSessionsFallback);
+
if (activeSessions.count() > 0) {
quInfo() << "Restoring previous core state...";
foreach(QVariant v, activeSessions) {
}
+void PostgreSqlStorage::setCoreState(const QVariantList &data)
+{
+ QByteArray rawData;
+ QDataStream out(&rawData, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_4_2);
+ out << data;
+
+ QSqlDatabase db = logDb();
+ QSqlQuery selectQuery(db);
+ selectQuery.prepare(queryString("select_core_state"));
+ selectQuery.bindValue(":key", "active_sessions");
+ safeExec(selectQuery);
+ watchQuery(selectQuery);
+
+ QString setQueryString;
+ if (!selectQuery.first()) {
+ setQueryString = queryString("insert_core_state");
+ }
+ else {
+ setQueryString = queryString("update_core_state");
+ }
+
+ QSqlQuery setQuery(db);
+ setQuery.prepare(setQueryString);
+ setQuery.bindValue(":key", "active_sessions");
+ setQuery.bindValue(":value", rawData);
+ safeExec(setQuery);
+ watchQuery(setQuery);
+}
+
+
+QVariantList PostgreSqlStorage::getCoreState(const QVariantList &defaultData)
+{
+ QSqlQuery query(logDb());
+ query.prepare(queryString("select_core_state"));
+ query.bindValue(":key", "active_sessions");
+ safeExec(query);
+ watchQuery(query);
+
+ if (query.first()) {
+ QVariantList data;
+ QByteArray rawData = query.value(0).toByteArray();
+ QDataStream in(&rawData, QIODevice::ReadOnly);
+ in.setVersion(QDataStream::Qt_4_2);
+ in >> data;
+ return data;
+ } else {
+ return defaultData;
+ }
+}
+
+
IdentityId PostgreSqlStorage::createIdentity(UserId user, CoreIdentity &identity)
{
IdentityId identityId;
case UserSetting:
query = queryString("migrate_write_usersetting");
break;
+ case CoreState:
+ query = queryString("migrate_write_corestate");
+ break;
}
newQuery(query, logDb());
return true;
return exec();
}
+bool PostgreSqlMigrationWriter::writeMo(const CoreStateMO &coreState)
+{
+ bindValue(0, coreState.key);
+ bindValue(1, coreState.value);
+ return exec();
+}
+
bool PostgreSqlMigrationWriter::postProcess()
{
void delUser(UserId user) override;
void setUserSetting(UserId userId, const QString &settingName, const QVariant &data) override;
QVariant getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData = QVariant()) override;
+ void setCoreState(const QVariantList &data) override;
+ QVariantList getCoreState(const QVariantList &data) override;
/* Identity handling */
IdentityId createIdentity(UserId user, CoreIdentity &identity) override;
bool writeMo(const BacklogMO &backlog) override;
bool writeMo(const IrcServerMO &ircserver) override;
bool writeMo(const UserSettingMO &userSetting) override;
+ bool writeMo(const CoreStateMO &coreState) override;
bool prepareQuery(MigrationObject mo) override;
<file>./SQL/PostgreSQL/delete_nicks.sql</file>
<file>./SQL/PostgreSQL/delete_quasseluser.sql</file>
<file>./SQL/PostgreSQL/insert_buffer.sql</file>
+ <file>./SQL/PostgreSQL/insert_core_state.sql</file>
<file>./SQL/PostgreSQL/insert_identity.sql</file>
<file>./SQL/PostgreSQL/insert_message.sql</file>
<file>./SQL/PostgreSQL/insert_network.sql</file>
<file>./SQL/PostgreSQL/insert_user_setting.sql</file>
<file>./SQL/PostgreSQL/migrate_write_backlog.sql</file>
<file>./SQL/PostgreSQL/migrate_write_buffer.sql</file>
+ <file>./SQL/PostgreSQL/migrate_write_corestate.sql</file>
<file>./SQL/PostgreSQL/migrate_write_identity.sql</file>
<file>./SQL/PostgreSQL/migrate_write_identity_nick.sql</file>
<file>./SQL/PostgreSQL/migrate_write_ircserver.sql</file>
<file>./SQL/PostgreSQL/select_buffers_for_network.sql</file>
<file>./SQL/PostgreSQL/select_checkidentity.sql</file>
<file>./SQL/PostgreSQL/select_connected_networks.sql</file>
+ <file>./SQL/PostgreSQL/select_core_state.sql</file>
<file>./SQL/PostgreSQL/select_identities.sql</file>
<file>./SQL/PostgreSQL/select_internaluser.sql</file>
<file>./SQL/PostgreSQL/select_messagesAll.sql</file>
<file>./SQL/PostgreSQL/setup_120_alter_messageid_seq.sql</file>
<file>./SQL/PostgreSQL/setup_130_function_lastmsgid.sql</file>
<file>./SQL/PostgreSQL/setup_140_sender_idx.sql</file>
+ <file>./SQL/PostgreSQL/setup_150_corestate.sql</file>
<file>./SQL/PostgreSQL/update_backlog_bufferid.sql</file>
<file>./SQL/PostgreSQL/update_buffer_bufferactivity.sql</file>
<file>./SQL/PostgreSQL/update_buffer_cipher.sql</file>
<file>./SQL/PostgreSQL/update_buffer_name.sql</file>
<file>./SQL/PostgreSQL/update_buffer_persistent_channel.sql</file>
<file>./SQL/PostgreSQL/update_buffer_set_channel_key.sql</file>
+ <file>./SQL/PostgreSQL/update_core_state.sql</file>
<file>./SQL/PostgreSQL/update_identity.sql</file>
<file>./SQL/PostgreSQL/update_network.sql</file>
<file>./SQL/PostgreSQL/update_network_connected.sql</file>
<file>./SQL/PostgreSQL/version/27/upgrade_010_update_sender_add_avatarurl.sql</file>
<file>./SQL/PostgreSQL/version/27/upgrade_020_update_sender_add_new_constraint.sql</file>
<file>./SQL/PostgreSQL/version/27/upgrade_030_upgrade_sender_drop_old_constraint.sql</file>
+ <file>./SQL/PostgreSQL/version/28/upgrade_000_create_corestate.sql</file>
<file>./SQL/SQLite/delete_backlog_by_uid.sql</file>
<file>./SQL/SQLite/delete_backlog_for_buffer.sql</file>
<file>./SQL/SQLite/delete_backlog_for_network.sql</file>
<file>./SQL/SQLite/delete_nicks.sql</file>
<file>./SQL/SQLite/delete_quasseluser.sql</file>
<file>./SQL/SQLite/insert_buffer.sql</file>
+ <file>./SQL/SQLite/insert_core_state.sql</file>
<file>./SQL/SQLite/insert_identity.sql</file>
<file>./SQL/SQLite/insert_message.sql</file>
<file>./SQL/SQLite/insert_network.sql</file>
<file>./SQL/SQLite/insert_user_setting.sql</file>
<file>./SQL/SQLite/migrate_read_backlog.sql</file>
<file>./SQL/SQLite/migrate_read_buffer.sql</file>
+ <file>./SQL/SQLite/migrate_read_corestate.sql</file>
<file>./SQL/SQLite/migrate_read_identity.sql</file>
<file>./SQL/SQLite/migrate_read_identity_nick.sql</file>
<file>./SQL/SQLite/migrate_read_ircserver.sql</file>
<file>./SQL/SQLite/select_buffers_for_network.sql</file>
<file>./SQL/SQLite/select_checkidentity.sql</file>
<file>./SQL/SQLite/select_connected_networks.sql</file>
+ <file>./SQL/SQLite/select_core_state.sql</file>
<file>./SQL/SQLite/select_identities.sql</file>
<file>./SQL/SQLite/select_internaluser.sql</file>
<file>./SQL/SQLite/select_messagesAll.sql</file>
<file>./SQL/SQLite/setup_130_identity.sql</file>
<file>./SQL/SQLite/setup_140_identity_nick.sql</file>
<file>./SQL/SQLite/setup_150_sender_idx.sql</file>
+ <file>./SQL/SQLite/setup_160_corestate.sql</file>
<file>./SQL/SQLite/update_backlog_bufferid.sql</file>
<file>./SQL/SQLite/update_buffer_bufferactivity.sql</file>
<file>./SQL/SQLite/update_buffer_cipher.sql</file>
<file>./SQL/SQLite/update_buffer_name.sql</file>
<file>./SQL/SQLite/update_buffer_persistent_channel.sql</file>
<file>./SQL/SQLite/update_buffer_set_channel_key.sql</file>
+ <file>./SQL/SQLite/update_core_state.sql</file>
<file>./SQL/SQLite/update_identity.sql</file>
<file>./SQL/SQLite/update_network.sql</file>
<file>./SQL/SQLite/update_network_connected.sql</file>
<file>./SQL/SQLite/version/29/upgrade_020_drop_sender.sql</file>
<file>./SQL/SQLite/version/29/upgrade_030_rename_sender_tmp_sender.sql</file>
<file>./SQL/SQLite/version/29/upgrade_040_update_sender_add_realname_avatarurl.sql</file>
+ <file>./SQL/SQLite/version/30/upgrade_000_create_corestate.sql</file>
</qresource>
</RCC>
}
+void SqliteStorage::setCoreState(const QVariantList &data)
+{
+ QByteArray rawData;
+ QDataStream out(&rawData, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_4_2);
+ out << data;
+
+ QSqlDatabase db = logDb();
+ db.transaction();
+ {
+ QSqlQuery query(db);
+ query.prepare(queryString("insert_core_state"));
+ query.bindValue(":key", "active_sessions");
+ query.bindValue(":value", rawData);
+ lockForWrite();
+ safeExec(query);
+
+ if (query.lastError().isValid()) {
+ QSqlQuery updateQuery(db);
+ updateQuery.prepare(queryString("update_core_state"));
+ updateQuery.bindValue(":key", "active_sessions");
+ updateQuery.bindValue(":value", rawData);
+ safeExec(updateQuery);
+ }
+ db.commit();
+ }
+ unlock();
+}
+
+
+QVariantList SqliteStorage::getCoreState(const QVariantList &defaultData)
+{
+ QVariantList data;
+ {
+ QSqlQuery query(logDb());
+ query.prepare(queryString("select_core_state"));
+ query.bindValue(":key", "active_sessions");
+ lockForRead();
+ safeExec(query);
+
+ if (query.first()) {
+ QByteArray rawData = query.value(0).toByteArray();
+ QDataStream in(&rawData, QIODevice::ReadOnly);
+ in.setVersion(QDataStream::Qt_4_2);
+ in >> data;
+ } else {
+ data = defaultData;
+ }
+ }
+ unlock();
+ return data;
+}
+
+
IdentityId SqliteStorage::createIdentity(UserId user, CoreIdentity &identity)
{
IdentityId identityId;
case UserSetting:
newQuery(queryString("migrate_read_usersetting"), logDb());
break;
+ case CoreState:
+ newQuery(queryString("migrate_read_corestate"), logDb());
+ break;
}
return exec();
}
return true;
}
+
+
+bool SqliteMigrationReader::readMo(CoreStateMO &coreState)
+{
+ if (!next())
+ return false;
+
+ coreState.key = value(0).toString();
+ coreState.value = value(1).toByteArray();
+
+ return true;
+}
void delUser(UserId user) override;
void setUserSetting(UserId userId, const QString &settingName, const QVariant &data) override;
QVariant getUserSetting(UserId userId, const QString &settingName, const QVariant &defaultData = QVariant()) override;
+ void setCoreState(const QVariantList &data) override;
+ QVariantList getCoreState(const QVariantList &data) override;
/* Identity handling */
IdentityId createIdentity(UserId user, CoreIdentity &identity) override;
bool readMo(BacklogMO &backlog) override;
bool readMo(IrcServerMO &ircserver) override;
bool readMo(UserSettingMO &userSetting) override;
+ bool readMo(CoreStateMO &coreState) override;
bool prepareQuery(MigrationObject mo) override;
*/
virtual QVariant getUserSetting(UserId userId, const QString &settingName, const QVariant &data = QVariant()) = 0;
+ //! Store core state
+ /**
+ * \param data Active Sessions
+ */
+ virtual void setCoreState(const QVariantList &data) = 0;
+
+ //! Retrieve core state
+ /**
+ * \param default Value to return in case it's unset.
+ * \return Active Sessions
+ */
+ virtual QVariantList getCoreState(const QVariantList &data = QVariantList()) = 0;
+
/* Identity handling */
virtual IdentityId createIdentity(UserId user, CoreIdentity &identity) = 0;
virtual bool updateIdentity(UserId user, const CoreIdentity &identity) = 0;