2017-02-26 19:29:50 +01:00
# include " ConnectionListModel.h "
2017-08-23 13:27:23 +02:00
# include "ScopeGuard.h"
2017-02-26 19:29:50 +01:00
# include "util.h"
2017-01-14 20:07:12 +01:00
2017-02-26 19:29:50 +01:00
# include <botan/cryptobox.h>
2019-08-24 20:47:32 +02:00
# include <QDir>
2019-09-16 19:24:39 +02:00
# include <QMimeData>
2019-08-24 20:47:32 +02:00
# include <QSettings>
2019-08-25 15:33:51 +02:00
# include <QSqlDatabase>
# include <QSqlError>
# include <QSqlQuery>
# include <QStandardPaths>
2019-09-16 19:24:39 +02:00
# include <QStringBuilder>
2017-01-14 20:07:12 +01:00
2019-08-24 20:47:32 +02:00
namespace {
2019-08-25 15:33:51 +02:00
const char * const q_create_table_conngroup =
R " __(
CREATE TABLE IF NOT EXISTS conngroup (
conngroup_id INTEGER PRIMARY KEY ,
2019-08-27 20:12:00 +02:00
gname TEXT NOT NULL UNIQUE
2019-08-25 15:33:51 +02:00
) ; ) __ " ;
const char * const q_create_table_connection =
R " __(
CREATE TABLE IF NOT EXISTS connection (
uuid TEXT PRIMARY KEY ,
2019-09-16 19:24:39 +02:00
cname TEXT ,
2019-08-25 15:33:51 +02:00
conngroup_id INTEGER NOT NULL ,
2019-09-16 19:24:39 +02:00
host TEXT ,
hostaddr TEXT ,
2019-08-25 15:33:51 +02:00
port INTEGER NOT NULL ,
2019-09-16 19:24:39 +02:00
user TEXT ,
dbname TEXT ,
2019-08-25 15:33:51 +02:00
sslmode INTEGER NOT NULL ,
2019-09-16 19:24:39 +02:00
sslcert TEXT ,
sslkey TEXT ,
sslrootcert TEXT ,
sslcrl TEXT ,
password TEXT
2019-08-25 15:33:51 +02:00
) ; ) __ " ;
2019-08-27 20:12:00 +02:00
2019-08-25 15:33:51 +02:00
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 ,
2019-09-01 14:07:58 +02:00
: sslmode , : sslcert , : sslkey , : sslrootcert , : sslcrl , : password ) ;
2019-08-25 15:33:51 +02:00
) __ " ;
std : : tuple < bool , QSqlError > 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 , { } } ;
}
2019-08-27 20:12:00 +02:00
std : : optional < QSqlError > SaveConnectionConfig ( QSqlDatabase & db , const ConnectionConfig & cc , int conngroup_id )
2019-08-25 15:33:51 +02:00
{
QSqlQuery q ( db ) ;
q . prepare ( q_insert_or_replace_into_connection ) ;
q . bindValue ( " :uuid " , cc . uuid ( ) . toString ( ) ) ;
2019-09-16 19:24:39 +02:00
q . bindValue ( " :name " , cc . name ( ) ) ;
2019-08-25 15:33:51 +02:00
q . bindValue ( " :conngroup_id " , conngroup_id ) ;
2019-09-16 19:24:39 +02:00
q . bindValue ( " :host " , cc . host ( ) ) ;
q . bindValue ( " :hostaddr " , cc . hostAddr ( ) ) ;
q . bindValue ( " :port " , ( int ) cc . port ( ) ) ;
q . bindValue ( " :user " , cc . user ( ) ) ;
q . bindValue ( " :dbname " , cc . dbname ( ) ) ;
2019-08-25 15:33:51 +02:00
q . bindValue ( " :sslmode " , static_cast < int > ( cc . sslMode ( ) ) ) ;
2019-09-16 19:24:39 +02:00
q . bindValue ( " :sslcert " , cc . sslCert ( ) ) ;
q . bindValue ( " :sslkey " , cc . sslKey ( ) ) ;
q . bindValue ( " :sslrootcert " , cc . sslRootCert ( ) ) ;
q . bindValue ( " :sslcrl " , cc . sslCrl ( ) ) ;
2022-09-05 14:33:51 +02:00
auto & encodedPassword = cc . encodedPassword ( ) ;
if ( encodedPassword . isEmpty ( ) )
q . bindValue ( " :password " , QVariant ( ) ) ;
else
q . bindValue ( " :password " , encodedPassword ) ;
2019-08-25 15:33:51 +02:00
if ( ! q . exec ( ) ) {
2019-09-16 19:24:39 +02:00
auto sql_error = q . lastError ( ) ;
return { sql_error } ;
2019-08-25 15:33:51 +02:00
}
2019-08-27 20:12:00 +02:00
return { } ;
2019-08-25 15:33:51 +02:00
}
2019-08-24 20:47:32 +02:00
} // end of unnamed namespace
2019-08-25 15:33:51 +02:00
ConnectionTreeModel : : ConnectionTreeModel ( QObject * parent , QSqlDatabase & db )
: QAbstractItemModel ( parent )
, m_db ( db )
{
}
void ConnectionTreeModel : : load ( )
{
InitConnectionTables ( m_db ) ;
2019-08-27 20:12:00 +02:00
QSqlQuery q ( m_db ) ;
q . prepare ( " SELECT conngroup_id, gname FROM conngroup; " ) ;
if ( ! q . exec ( ) ) {
// auto err = q_create_table.lastError();
// return { false, err };
throw std : : runtime_error ( " Loading groups failed " ) ;
}
while ( q . next ( ) ) {
int id = q . value ( 0 ) . toInt ( ) ;
QString name = q . value ( 1 ) . toString ( ) ;
auto g = std : : make_shared < ConnectionGroup > ( ) ;
g - > conngroup_id = id ;
g - > name = name ;
m_groups . push_back ( g ) ;
2019-08-25 15:33:51 +02:00
}
2019-08-27 20:12:00 +02:00
q . prepare ( " SELECT uuid, cname, conngroup_id, host, hostaddr, port, "
" user, dbname, sslmode, sslcert, sslkey, sslrootcert, sslcrl, "
2019-09-01 14:07:58 +02:00
" password "
2019-08-27 20:12:00 +02:00
" FROM connection ORDER BY conngroup_id, cname; " ) ;
if ( ! q . exec ( ) ) {
// auto err = q_create_table.lastError();
// return { false, err };
throw std : : runtime_error ( " Loading groups failed " ) ;
}
while ( q . next ( ) ) {
2019-08-25 15:33:51 +02:00
auto cc = std : : make_shared < ConnectionConfig > ( ) ;
2019-08-27 20:12:00 +02:00
cc - > setUuid ( q . value ( 0 ) . toUuid ( ) ) ;
2019-09-16 19:24:39 +02:00
cc - > setName ( q . value ( 1 ) . toString ( ) ) ;
cc - > setHost ( q . value ( 3 ) . toString ( ) ) ;
cc - > setHostAddr ( q . value ( 4 ) . toString ( ) ) ;
2019-08-27 20:12:00 +02:00
cc - > setPort ( static_cast < uint16_t > ( q . value ( 5 ) . toInt ( ) ) ) ;
2019-09-16 19:24:39 +02:00
cc - > setUser ( q . value ( 6 ) . toString ( ) ) ;
cc - > setDbname ( q . value ( 7 ) . toString ( ) ) ;
2019-08-27 20:12:00 +02:00
cc - > setSslMode ( static_cast < SslMode > ( q . value ( 8 ) . toInt ( ) ) ) ;
2019-09-16 19:24:39 +02:00
cc - > setSslCert ( q . value ( 9 ) . toString ( ) ) ;
cc - > setSslKey ( q . value ( 10 ) . toString ( ) ) ;
cc - > setSslRootCert ( q . value ( 11 ) . toString ( ) ) ;
cc - > setSslCrl ( q . value ( 12 ) . toString ( ) ) ;
cc - > setEncodedPassword ( q . value ( 13 ) . toByteArray ( ) ) ;
2019-08-27 20:12:00 +02:00
int group_id = q . value ( 2 ) . toInt ( ) ;
auto find_res = std : : find_if ( m_groups . begin ( ) , m_groups . end ( ) ,
[ group_id ] ( auto item ) { return item - > conngroup_id = = group_id ; } ) ;
if ( find_res ! = m_groups . end ( ) ) {
( * find_res ) - > add ( cc ) ;
}
else {
throw std : : runtime_error ( " conngroup missing " ) ;
}
2019-08-25 15:33:51 +02:00
}
}
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 < ConnectionNode * > ( index . internalPointer ( ) ) ;
if ( auto group = dynamic_cast < ConnectionGroup * > ( privdata ) ; group ! = nullptr ) {
// This is a group
if ( role = = Qt : : DisplayRole ) {
if ( index . column ( ) = = Name ) {
v = group - > name ;
}
}
}
else if ( auto conn = dynamic_cast < ConnectionConfig * > ( privdata ) ; conn ! = nullptr ) {
// This is a connection
if ( role = = Qt : : DisplayRole ) {
switch ( index . column ( ) ) {
2019-09-16 19:24:39 +02:00
case Name : v = conn - > name ( ) ; break ;
case Host : v = conn - > host ( ) ; break ;
2019-08-25 15:33:51 +02:00
case Port : v = conn - > port ( ) ; break ;
2019-09-16 19:24:39 +02:00
case User : v = conn - > user ( ) ; break ;
case DbName : v = conn - > dbname ( ) ; break ;
2019-08-25 15:33:51 +02:00
}
}
}
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 < ConnectionNode * > ( parent . internalPointer ( ) ) ;
if ( auto group = dynamic_cast < ConnectionGroup * > ( 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 < ConnectionNode * > ( node ) ) ;
}
QModelIndex ConnectionTreeModel : : parent ( const QModelIndex & index ) const
{
if ( ! index . isValid ( ) )
return { } ;
auto privdata = static_cast < ConnectionNode * > ( index . internalPointer ( ) ) ;
if ( auto group = dynamic_cast < ConnectionGroup * > ( privdata ) ; group ! = nullptr ) {
return { } ;
}
else if ( auto config = dynamic_cast < ConnectionConfig * > ( 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 ( ) ) {
2019-08-27 20:12:00 +02:00
return createIndex ( find_res - m_groups . begin ( ) , 0 ,
const_cast < ConnectionGroup * > ( config - > parent ( ) ) ) ;
2019-08-25 15:33:51 +02:00
}
}
throw std : : logic_error ( " Should never get here " ) ;
}
int ConnectionTreeModel : : rowCount ( const QModelIndex & parent ) const
{
int result = 0 ;
if ( parent . isValid ( ) ) {
auto privdata = static_cast < ConnectionNode * > ( parent . internalPointer ( ) ) ;
if ( auto group = dynamic_cast < ConnectionGroup * > ( privdata ) ; group ! = nullptr ) {
result = group - > connections ( ) . size ( ) ;
}
else if ( auto config = dynamic_cast < ConnectionConfig * > ( privdata ) ; config ! = nullptr ) {
result = 0 ;
}
}
else {
result = m_groups . size ( ) ;
}
return result ;
}
2019-08-27 20:12:00 +02:00
int ConnectionTreeModel : : columnCount ( const QModelIndex & ) const
2019-08-25 15:33:51 +02:00
{
return ColCount ;
}
2019-08-27 20:12:00 +02:00
bool ConnectionTreeModel : : removeRows ( int row , int count , const QModelIndex & parent )
{
if ( parent . isValid ( ) & & count = = 1 ) {
// should be a group
auto grp = m_groups [ parent . row ( ) ] ;
for ( int i = 0 ; i < count ; + + i ) {
QUuid uuid = grp - > connections ( ) . at ( row + i ) - > uuid ( ) ;
QSqlQuery q ( m_db ) ;
q . prepare (
" DELETE FROM connection "
" WHERE uuid=:uuid " ) ;
q . bindValue ( " :uuid " , uuid ) ;
if ( ! q . exec ( ) ) {
auto err = q . lastError ( ) ;
throw std : : runtime_error ( " QqlError " ) ;
}
}
beginRemoveRows ( parent , row , row + count - 1 ) ;
SCOPE_EXIT { endRemoveRows ( ) ; } ;
grp - > erase ( row , count ) ;
2019-09-01 10:26:42 +02:00
return true ;
2019-08-27 20:12:00 +02:00
}
2019-09-01 10:26:42 +02:00
return false ;
2019-08-27 20:12:00 +02:00
}
void ConnectionTreeModel : : save ( const QString & group_name , const ConnectionConfig & cc )
{
auto [ grp_idx , conn_idx ] = findConfig ( cc . uuid ( ) ) ;
if ( grp_idx > = 0 ) {
auto grp = m_groups [ grp_idx ] ;
if ( grp - > name = = group_name ) {
// update config
grp - > update ( conn_idx , cc ) ;
// send change event
auto node = grp - > connections ( ) . at ( conn_idx ) ;
dataChanged (
createIndex ( conn_idx , 0 , node . get ( ) ) ,
createIndex ( conn_idx , ColCount - 1 , node . get ( ) ) ) ;
saveToDb ( * node ) ;
return ;
}
else {
auto parent = createIndex ( grp_idx , 0 , grp . get ( ) ) ;
beginRemoveRows ( parent , conn_idx , conn_idx ) ;
SCOPE_EXIT { endRemoveRows ( ) ; } ;
grp - > erase ( conn_idx ) ;
}
}
// Here we can assume we have to find the new group or create a new group
// because if the connection was in the right group the function has already returned.
// We assume the model is in sync with the DB as the DB should not be shared!
int new_grp_idx = findGroup ( group_name ) ;
if ( new_grp_idx < 0 ) {
// Group not found we are g
auto add_grp_res = addGroup ( group_name ) ;
if ( std : : holds_alternative < int > ( add_grp_res ) ) {
new_grp_idx = std : : get < int > ( add_grp_res ) ;
}
else {
throw std : : runtime_error ( " SqlError1 " ) ;
}
}
auto new_grp = m_groups [ new_grp_idx ] ;
auto parent = createIndex ( new_grp_idx , 0 , new_grp . get ( ) ) ;
auto idx = new_grp - > connections ( ) . size ( ) ;
beginInsertRows ( parent , idx , idx ) ;
SCOPE_EXIT { endInsertRows ( ) ; } ;
auto node = std : : make_shared < ConnectionConfig > ( cc ) ;
2019-09-01 06:48:34 +02:00
new_grp - > add ( node ) ;
2019-08-27 20:12:00 +02:00
auto save_res = saveToDb ( * node ) ;
if ( save_res ) {
2019-09-16 19:24:39 +02:00
QString msg = save_res - > text ( )
% " \n " % save_res - > driverText ( )
% " \n " % save_res - > databaseText ( ) ;
throw std : : runtime_error ( msg . toUtf8 ( ) . data ( ) ) ;
2019-08-27 20:12:00 +02:00
}
}
void ConnectionTreeModel : : save ( const ConnectionConfig & cc )
{
2022-09-05 07:33:08 +02:00
saveToDb ( cc ) ;
}
void ConnectionTreeModel : : clearAllPasswords ( )
{
for ( auto group : m_groups )
for ( auto cc : group - > connections ( ) )
{
cc - > setEncodedPassword ( { } ) ;
saveToDb ( * cc ) ;
}
2019-08-27 20:12:00 +02:00
}
std : : tuple < int , int > ConnectionTreeModel : : findConfig ( const QUuid uuid ) const
{
int group_idx = - 1 , connection_idx = - 1 ;
for ( int grp_idx = 0 ; grp_idx < m_groups . size ( ) ; + + grp_idx ) {
auto & & grp = m_groups [ grp_idx ] ;
auto & & conns = grp - > connections ( ) ;
auto find_res = std : : find_if ( conns . begin ( ) , conns . end ( ) ,
[ & uuid ] ( auto item ) - > bool { return item - > uuid ( ) = = uuid ; } ) ;
if ( find_res ! = conns . end ( ) ) {
group_idx = grp_idx ;
connection_idx = find_res - conns . begin ( ) ;
break ;
}
}
return { group_idx , connection_idx } ;
}
2019-09-02 16:33:13 +02:00
int ConnectionTreeModel : : findGroup ( const QString & name ) const
2019-08-27 20:12:00 +02:00
{
for ( int idx = 0 ; idx < m_groups . size ( ) ; + + idx ) {
if ( m_groups [ idx ] - > name = = name ) return idx ;
}
return - 1 ;
}
2019-09-02 16:33:13 +02:00
std : : variant < int , QSqlError > ConnectionTreeModel : : addGroup ( const QString & group_name )
2019-08-27 20:12:00 +02:00
{
QSqlQuery q ( m_db ) ;
q . prepare ( " INSERT INTO conngroup (gname) VALUES (:name) " ) ;
q . bindValue ( " :name " , group_name ) ;
if ( ! q . exec ( ) ) {
auto err = q . lastError ( ) ;
return { err } ;
}
2019-09-01 06:48:11 +02:00
auto cg = std : : make_shared < ConnectionGroup > ( ) ;
cg - > conngroup_id = q . lastInsertId ( ) . toInt ( ) ;
cg - > name = group_name ;
int row = m_groups . size ( ) ;
beginInsertRows ( { } , row , row ) ;
SCOPE_EXIT { endInsertRows ( ) ; } ;
m_groups . push_back ( cg ) ;
return row ;
}
2019-09-01 06:42:21 +02:00
std : : optional < QSqlError > ConnectionTreeModel : : removeGroup ( int row )
{
beginRemoveRows ( { } , row , row ) ;
SCOPE_EXIT { endRemoveRows ( ) ; } ;
auto id = m_groups [ row ] - > conngroup_id ;
QSqlQuery q ( m_db ) ;
q . prepare ( " DELETE FROM connection WHERE conngroup_id=:id " ) ;
q . bindValue ( " :id " , id ) ;
if ( ! q . exec ( ) ) {
auto err = q . lastError ( ) ;
return { err } ;
}
q . prepare ( " DELETE FROM conngroup WHERE conngroup_id=:id " ) ;
q . bindValue ( " :id " , id ) ;
if ( ! q . exec ( ) ) {
auto err = q . lastError ( ) ;
return { err } ;
}
m_groups . remove ( row ) ;
return { } ;
}
2019-09-01 06:44:48 +02:00
int ConnectionTreeModel : : findGroup ( int conngroup_id ) const
{
auto find_res = std : : find_if ( m_groups . begin ( ) , m_groups . end ( ) ,
[ conngroup_id ] ( auto item ) { return item - > conngroup_id = = conngroup_id ; } ) ;
if ( find_res = = m_groups . end ( ) )
return - 1 ;
return find_res - m_groups . begin ( ) ;
2019-08-27 20:12:00 +02:00
}
2019-09-01 14:07:58 +02:00
ConnectionConfig * ConnectionTreeModel : : getConfigFromModelIndex ( QModelIndex index )
{
if ( ! index . isValid ( ) )
return nullptr ;
auto node = static_cast < ConnectionNode * > ( index . internalPointer ( ) ) ;
return dynamic_cast < ConnectionConfig * > ( node ) ;
}
ConnectionGroup * ConnectionTreeModel : : getGroupFromModelIndex ( QModelIndex index )
{
if ( ! index . isValid ( ) )
return nullptr ;
auto node = static_cast < ConnectionNode * > ( index . internalPointer ( ) ) ;
return dynamic_cast < ConnectionGroup * > ( node ) ;
}
2019-08-27 20:12:00 +02:00
std : : optional < QSqlError > ConnectionTreeModel : : saveToDb ( const ConnectionConfig & cc )
{
return SaveConnectionConfig ( m_db , cc , cc . parent ( ) - > conngroup_id ) ;
}
2019-09-16 19:24:39 +02:00
Qt : : DropActions ConnectionTreeModel : : supportedDropActions ( ) const
{
return Qt : : MoveAction ;
}
Qt : : DropActions ConnectionTreeModel : : supportedDragActions ( ) const
{
return Qt : : MoveAction ;
}
Qt : : ItemFlags ConnectionTreeModel : : flags ( const QModelIndex & index ) const
{
Qt : : ItemFlags defaultFlags = QAbstractItemModel : : flags ( index ) ;
ConnectionConfig * cfg = getConfigFromModelIndex ( index ) ;
if ( cfg )
return Qt : : ItemIsDragEnabled | defaultFlags ;
else
return Qt : : ItemIsDropEnabled | defaultFlags ;
}
//bool ConnectionTreeModel::insertRows(int row, int count, const QModelIndex &parent)
//{
// return false;
//}
//bool ConnectionTreeModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
//{
// return false;
//}
namespace {
const auto mimeType = " application/vnd.pgLab.connection " ;
}
QStringList ConnectionTreeModel : : mimeTypes ( ) const
{
return { mimeType } ;
}
QMimeData * ConnectionTreeModel : : mimeData ( const QModelIndexList & indexes ) const
{
QMimeData * mimeData = new QMimeData ;
QByteArray encodedData ;
QDataStream stream ( & encodedData , QIODevice : : WriteOnly ) ;
for ( const QModelIndex & index : indexes ) {
if ( index . isValid ( ) ) {
QString text = data ( index , Qt : : DisplayRole ) . toString ( ) ;
stream < < text ;
}
}
mimeData - > setData ( mimeType , encodedData ) ;
return mimeData ;
}
bool ConnectionTreeModel : : dropMimeData ( const QMimeData * data , Qt : : DropAction action , int row , int column , const QModelIndex & parent )
{
return false ;
}