#include "ConnectionListModel.h" #include "ScopeGuard.h" #include "util.h" #include #include #include #include #include #include #include namespace { const char * const q_create_table_conngroup = R"__( CREATE TABLE IF NOT EXISTS conngroup ( conngroup_id INTEGER PRIMARY KEY, gname TEXT NOT NULL );)__"; const char * const q_create_table_connection = R"__( CREATE TABLE IF NOT EXISTS connection ( uuid TEXT PRIMARY KEY, cname TEXT NOT NULL, conngroup_id INTEGER NOT NULL, host TEXT NOT NULL, hostaddr TEXT NOT NULL, port INTEGER NOT NULL, user TEXT NOT NULL, dbname TEXT NOT NULL, sslmode INTEGER NOT NULL, sslcert TEXT NOT NULL, sslkey TEXT NOT NULL, sslrootcert TEXT NOT NULL, sslcrl TEXT NOT NULL, passwordstate INTEGER NOT NULL );)__"; const char * const q_insert_or_replace_into_connection = R"__(INSERT OR REPLACE INTO connection VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname, :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :passwordstate); )__" ; std::tuple InitConnectionTables(QSqlDatabase &db) { QSqlQuery q_create_table(db); q_create_table.prepare(q_create_table_conngroup); if (!q_create_table.exec()) { auto err = q_create_table.lastError(); return { false, err }; } q_create_table.prepare(q_create_table_connection); if (!q_create_table.exec()) { auto err = q_create_table.lastError(); return { false, err }; } return {true, {}}; } std::tuple SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) { QSqlQuery q(db); q.prepare(q_insert_or_replace_into_connection); q.bindValue(":uuid", cc.uuid().toString()); q.bindValue(":name", stdStrToQ(cc.name())); q.bindValue(":conngroup_id", conngroup_id); q.bindValue(":host", stdStrToQ(cc.host())); q.bindValue(":hostaddr", stdStrToQ(cc.hostAddr())); q.bindValue(":port", cc.port()); q.bindValue(":user", stdStrToQ(cc.user())); q.bindValue(":dbname", stdStrToQ(cc.dbname())); q.bindValue(":sslmode", static_cast(cc.sslMode())); q.bindValue(":sslcert", stdStrToQ(cc.sslCert())); q.bindValue(":sslkey", stdStrToQ(cc.sslKey())); q.bindValue(":sslrootcert", stdStrToQ(cc.sslRootCert())); q.bindValue(":sslcrl", stdStrToQ(cc.sslCrl())); q.bindValue(":passwordstate", static_cast(cc.passwordState())); if (!q.exec()) { auto err = q.lastError(); return { false, err }; } return {true, {}}; } /** Saves a connection configuration. Before calling this you may want to call beginGroup. */ void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) { settings.setValue("name", stdStrToQ(cc.name())); settings.setValue("host", stdStrToQ(cc.host())); settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); settings.setValue("port", cc.port()); settings.setValue("user", stdStrToQ(cc.user())); //settings.setValue("password", stdStrToQ(cc.password())); settings.setValue("dbname", stdStrToQ(cc.dbname())); settings.setValue("sslmode", static_cast(cc.sslMode())); settings.setValue("sslcert", stdStrToQ(cc.sslCert())); settings.setValue("sslkey", stdStrToQ(cc.sslKey())); settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); settings.setValue("passwordState", static_cast(cc.passwordState())); } template bool in_range(T value) { return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); } void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) { cc.setName(qvarToStdStr(settings.value("name"))); cc.setHost(qvarToStdStr(settings.value("host"))); cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); int p = settings.value("port", 5432).toInt(); if (!in_range(p)) { p = 0; // let the user re-enter a valid value } cc.setPort(static_cast(p)); cc.setUser(qvarToStdStr(settings.value("user"))); //cc.setPassword(qvarToStdStr(settings.value("password"))); cc.setDbname(qvarToStdStr(settings.value("dbname"))); cc.setSslMode(static_cast(settings.value("sslmode").toInt())); cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); PasswordState pwstate; QVariant v = settings.value("passwordState"); if (v.isNull()) pwstate = PasswordState::NotStored; else pwstate = static_cast(v.toInt()); cc.setPasswordState(pwstate); } } // end of unnamed namespace ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) { } ConnectionListModel::~ConnectionListModel() = default; int ConnectionListModel::rowCount(const QModelIndex &parent) const { int result = 0; if (parent == QModelIndex()) { result = m_connections.size(); } return result; } int ConnectionListModel::columnCount(const QModelIndex &/*parent*/) const { return ColCount; } QVariant ConnectionListModel::data(const QModelIndex &index, int role) const { QVariant result; if (role == Qt::DisplayRole || role == Qt::EditRole) { int row = index.row(); int col = index.column(); const ConnectionConfig& cfg = m_connections.at(row); switch (col) { case Description: result = makeLongDescription(cfg); break; case Name: result = stdStrToQ(cfg.name()); break; case Host: result = stdStrToQ(cfg.host()); break; case Port: result = cfg.port(); break; case User: result = stdStrToQ(cfg.user()); break; case Password: result = stdStrToQ(cfg.password()); break; case DbName: result = stdStrToQ(cfg.dbname()); break; } } return result; } bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool result = false; if (role == Qt::EditRole) { int row = index.row(); int col = index.column(); // auto& elem = m_connections.at(row); // elem.m_dirty = true; // ConnectionConfig& cfg = elem.m_config; ConnectionConfig& cfg = m_connections[row]; if (col > 0) { result = true; } switch (col) { case Description: break; case Name: cfg.setName( qStrToStd(value.toString()) ); break; case Host: cfg.setHost( qStrToStd(value.toString()) ); break; case Port: cfg.setPort( value.toInt() ); break; case User: cfg.setUser( qStrToStd(value.toString()) ); break; case Password: cfg.setPassword( qStrToStd(value.toString()) ); break; case DbName: cfg.setDbname( qStrToStd(value.toString()) ); break; } } if (result) { emit dataChanged(index, index); } return result; } Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const { Qt::ItemFlags result; int row = index.row(); if (row >= 0 && row < m_connections.size()) { result = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (index.column() != Description) result |= Qt::ItemIsEditable; } return result; } QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg) { std::string result(cfg.name()); result += " ("; result += cfg.user(); result += "@"; result += cfg.host(); result += ":"; result += std::to_string(cfg.port()); result += "/"; result += cfg.dbname(); result += ")"; return stdStrToQ(result); } bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) { bool result = false; if (row >= 0 && row < m_connections.size()) { beginRemoveRows(parent, row, row + count -1); SCOPE_EXIT { endRemoveRows(); }; QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); for (int idx = 0; idx < count; ++idx) { auto&& cc = m_connections[idx+row]; settings.remove(cc.uuid().toString()); } settings.sync(); m_connections.remove(row, count); result = true; } return result; } //void ConnectionListModel::newItem() //{ //// int i = m_connections->createNew(); //// auto idx = createIndex(i, 0); //// emit dataChanged(idx, idx); //} Expected ConnectionListModel::get(int row) { if (row < m_connections.size()) { return m_connections.at(row); } return Expected::fromException(std::out_of_range("Invalid row")); } void ConnectionListModel::load() { QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); auto groups = settings.childGroups(); for (auto&& grp : groups) { if (grp == "c_IniGroupSecurity") { // Read security settings } else { QUuid uuid(grp); if ( ! uuid.isNull() ) { settings.beginGroup(grp); SCOPE_EXIT { settings.endGroup(); }; ConnectionConfig cc; cc.setUuid(uuid); LoadConnectionConfig(settings, cc); m_connections.push_back(cc); } } } } void ConnectionListModel::save() { QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); for (auto& e : m_connections) { settings.beginGroup(e.uuid().toString()); SCOPE_EXIT { settings.endGroup(); }; SaveConnectionConfig(settings, e); e.clean(); } settings.sync(); } void ConnectionListModel::save(int index) { auto& e = m_connections[index]; if (e.dirty()) { QString file_name = iniFileName(); QSettings settings(file_name, QSettings::IniFormat); settings.beginGroup(e.uuid().toString()); SaveConnectionConfig(settings, e); e.clean(); settings.sync(); } } void ConnectionListModel::save(const ConnectionConfig &cc) { auto find_res = std::find(m_connections.begin(), m_connections.end(), cc.uuid()); int i; if (find_res == m_connections.end()) { m_connections.push_back(cc); i = m_connections.size() - 1; } else { *find_res = cc; i = find_res - m_connections.begin(); } emit dataChanged(createIndex(i, 0), createIndex(i, ColCount-1)); save(i); } QString ConnectionListModel::iniFileName() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } path += "/connections.ini"; return path; } ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) : QAbstractItemModel(parent) , m_db(db) { } void ConnectionTreeModel::load() { InitConnectionTables(m_db); auto g1 = std::make_shared(); g1->name = "Testing"; for (int i = 1; i < 3; ++i) { auto cc = std::make_shared(); cc->setUuid(QUuid::createUuid()); cc->setName("testconn " + std::to_string(i)); g1->add(cc); } auto g2 = std::make_shared(); g2->name = "Production"; for (int i = 1; i < 4; ++i) { auto cc = std::make_shared(); cc->setUuid(QUuid::createUuid()); cc->setName("prodconn " + std::to_string(i)); g2->add(cc); } m_groups = { g1, g2 }; } QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const { // Code below assumes two level tree groups/connections // it will fail for nested groups QVariant v; auto privdata = static_cast(index.internalPointer()); if (auto group = dynamic_cast(privdata); group != nullptr) { // This is a group if (role == Qt::DisplayRole) { if (index.column() == Name) { v = group->name; } } } else if (auto conn = dynamic_cast(privdata); conn != nullptr) { // This is a connection if (role == Qt::DisplayRole) { switch (index.column()) { case Name: v = stdStrToQ(conn->name()); break; case Host: v = stdStrToQ(conn->host()); break; case Port: v = conn->port(); break; case User: v = stdStrToQ(conn->user()); break; case DbName: v= stdStrToQ(conn->dbname()); break; } } } return v; } QVariant ConnectionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant v; if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { switch (section) { case Name: v = tr("Name"); break; case Host: v = tr("Host"); break; case Port: v = tr("Port"); break; case User: v = tr("User"); break; case DbName: v= tr("Database"); break; } } } return v; } QModelIndex ConnectionTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return {}; const ConnectionNode *node = nullptr; if (parent.isValid()) { auto privdata = static_cast(parent.internalPointer()); if (auto group = dynamic_cast(privdata); group != nullptr) { node = group->connections().at(row).get(); } else { throw std::logic_error("Should never ask for a child index of a connectionconfig"); } } else { node = m_groups[row].get(); } return createIndex(row, column, const_cast(node)); // void *p = nullptr; // if (parent.isValid()) { // auto cg = static_cast(parent.internalPointer()); // auto cc = &cg->connections().at(row); // p = const_cast(cc); // } // else { // p = const_cast(&m_groups.at(row)); // } // return createIndex(row, column, p); } QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const { if (!index.isValid()) return {}; auto privdata = static_cast(index.internalPointer()); if (auto group = dynamic_cast(privdata); group != nullptr) { return {}; } else if (auto config = dynamic_cast(privdata); config != nullptr) { auto p = config->parent(); auto find_res = std::find_if(m_groups.begin(), m_groups.end(), [p] (auto item) -> bool { return *p == *item; }); if (find_res != m_groups.end()) { return createIndex(find_res - m_groups.begin(), 0, config->parent()); } } throw std::logic_error("Should never get here"); } int ConnectionTreeModel::rowCount(const QModelIndex &parent) const { int result = 0; if (parent.isValid()) { // if (parent.column() <= 0) { // result = m_groups[parent.row()].connections().size(); // } // else { // result = 1; // } auto privdata = static_cast(parent.internalPointer()); if (auto group = dynamic_cast(privdata); group != nullptr) { result = group->connections().size(); } else if (auto config = dynamic_cast(privdata); config != nullptr) { result = 0; } } else { result = m_groups.size(); } return result; } int ConnectionTreeModel::columnCount(const QModelIndex &parent) const { return ColCount; }