diff --git a/.gitignore b/.gitignore index f73482c..6433272 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ debug/ release/ +DIST/ Makefile Makefile.Debug Makefile.Release \ No newline at end of file diff --git a/Ivory.pro b/Ivory.pro index effb4cb..8769585 100644 --- a/Ivory.pro +++ b/Ivory.pro @@ -12,7 +12,7 @@ TARGET = Ivory TEMPLATE = app INCLUDEPATH += C:\prog\include - +DEFINES += WIN32_LEAN_AND_MEAN LIBS += c:\prog\lib\libpq.lib SOURCES += main.cpp\ @@ -24,7 +24,10 @@ SOURCES += main.cpp\ sqlhighlighter.cpp \ jsoncpp.cpp \ queryexplainmodel.cpp \ - explaintreemodelitem.cpp + explaintreemodelitem.cpp \ + asyncdbconnection.cpp \ + tsqueue.cpp \ + win32event.cpp HEADERS += mainwindow.h \ serverproperties.h \ @@ -33,7 +36,10 @@ HEADERS += mainwindow.h \ queryresultmodel.h \ sqlhighlighter.h \ queryexplainmodel.h \ - explaintreemodelitem.h + explaintreemodelitem.h \ + asyncdbconnection.h \ + tsqueue.h \ + win32event.h FORMS += mainwindow.ui \ serverproperties.ui diff --git a/asyncdbconnection.cpp b/asyncdbconnection.cpp new file mode 100644 index 0000000..289775b --- /dev/null +++ b/asyncdbconnection.cpp @@ -0,0 +1,61 @@ +#include "asyncdbconnection.h" + +ASyncDBConnection::ASyncDBConnection() +{ + +} + +void ASyncDBConnection::setupConnection(const std::string &connstring) +{} + +void ASyncDBConnection::closeConnection() +{} + +void ASyncDBConnection::setStateCallback(on_state_callback state_callback) +{} + + +void ASyncDBConnection::Thread::run() +{ + while (!terminateRequested) { + + // make or recover connection + if (makeConnection()) { + // send commands and receive results + communicate(); + } + + } + + // close connection +} + +bool ASyncDBConnection::Thread::makeConnection() +{ + while (!terminateRequested) { + + // start connecting + + // poll till complete or failed (we can get an abort command) + + // if connected return true + // else retry (unless we get a command to stop then return false) + } + return false; +} + +void ASyncDBConnection::Thread::communicate() +{ + while (!terminateRequested) { + // wait for something to do: + // - command to send to server + // - wait for results and (notifies can also come in) + // - pass each result on to the completion routine + // - notify comming in from the server + // - pass to notify callback + // - connection raises an error + // - return + // - stop signal + // - return + } +} diff --git a/asyncdbconnection.h b/asyncdbconnection.h new file mode 100644 index 0000000..81c1800 --- /dev/null +++ b/asyncdbconnection.h @@ -0,0 +1,68 @@ +#ifndef ASYNCDBCONNECTION_H +#define ASYNCDBCONNECTION_H + +#include "PgsqlConn.h" +#include +#include + +class ASyncDBConnection { +public: + enum class State { + NotConnected, + Connecting, + Connected, + QuerySend, + CancelSend + }; + + using on_result_callback = std::function; + using on_state_callback = std::function; + + ASyncDBConnection(); + + void setupConnection(const std::string &connstring); + void closeConnection(); + + void setStateCallback(on_state_callback state_callback); + + /** Sends command to the server. + + When the result is in and no result_task_queue was passed then on_result will be + called directly within the thread. + + If the command gives multiple results on_result will be called for each result. + */ + bool send(const std::string &command, on_result_callback on_result); + +private: + + class Command { + public: + std::string command; + on_result_callback on_result; + }; + + /// Contains all the members accessed by the thread + class Thread { + public: + on_state_callback m_state_callback; + std::string m_init_string; + + /// Is started as a seperate thread by ASyncDBConnection + void run(); + + /// Sends a cancel request to the DB server + void cancel(); + private: + + Pgsql::Connection m_connection; + bool terminateRequested = false; ///< is set when the thread should stop + + bool makeConnection(); + void communicate(); + }; + + Thread thread; +}; + +#endif // ASYNCDBCONNECTION_H diff --git a/mainwindow.cpp b/mainwindow.cpp index ebd7dd9..75e3d22 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -40,7 +40,6 @@ MainWindow::MainWindow(QWidget *parent) : ui->queryEdit->setFont(font); highlighter.reset(new SqlHighlighter(ui->queryEdit->document())); ui->queryEdit->setPlainText(test_query); - ui->connectionStringEdit->setText("user=postgres dbname=foutrapport password=admin"); QAction *action; @@ -60,10 +59,9 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() {} -void MainWindow::QueueTask(callable c) +void MainWindow::QueueTask(TSQueue::t_Callable c) { - std::lock_guard lg(m_mutexCallableQueue); - m_callableQueue.emplace_back(std::move(c)); + m_taskQueue.add(c); // Theoretically this needs to be only called if the queue was empty because otherwise it already would // be busy emptying the queue. For now however I think it is safer to call it just to make sure. QMetaObject::invokeMethod(this, "processCallableQueue", Qt::QueuedConnection); // queues on main thread @@ -71,20 +69,12 @@ void MainWindow::QueueTask(callable c) void MainWindow::processCallableQueue() { - bool empty; - callable c; - { // narrow scope for lock guard - std::lock_guard lg(m_mutexCallableQueue); - c = m_callableQueue.back(); - m_callableQueue.pop_back(); - empty = m_callableQueue.empty(); - } - - c(); - - if (!empty) { - // This gives other events a chance to be processed to keep the UI snappy. - QTimer::singleShot(0, this, SLOT(processCallableQueue())); + if (!m_taskQueue.empty()) { + auto c = m_taskQueue.pop(); + c(); + if (!m_taskQueue.empty()) { + QTimer::singleShot(0, this, SLOT(processCallableQueue())); + } } } @@ -148,6 +138,7 @@ void MainWindow::performQuery() queryCancel = std::move(connection->getCancel()); QString command = ui->queryEdit->toPlainText(); + std::thread([this,command]() { auto res = std::make_shared(connection->query(command)); diff --git a/mainwindow.h b/mainwindow.h index 0bf59ec..43c036c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,6 +1,7 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "tsqueue.h" #include #include #include @@ -36,20 +37,16 @@ class MainWindow : public QMainWindow Q_OBJECT public: - using callable = std::function; - explicit MainWindow(QWidget *parent = 0); ~MainWindow(); /* Meant to be called from other threads to pass a code block * that has to be executed in the context of the thread of the window. */ - void QueueTask(callable c); + void QueueTask(TSQueue::t_Callable c); private: - using t_CallableQueue = std::deque; - std::mutex m_mutexCallableQueue; - t_CallableQueue m_callableQueue; + TSQueue m_taskQueue; std::unique_ptr ui; diff --git a/tsqueue.cpp b/tsqueue.cpp new file mode 100644 index 0000000..0c5b3d3 --- /dev/null +++ b/tsqueue.cpp @@ -0,0 +1,34 @@ +#include "tsqueue.h" + +TSQueue::TSQueue() + : newData(Win32Event::Reset::Manual, Win32Event::Initial::Clear) +{} + +void TSQueue::add(t_Callable callable) +{ + std::lock_guard g(m); + futureQueue.push_back(std::move(callable)); + newData.Set(); +} + +bool TSQueue::empty() +{ + std::lock_guard g(m); + return futureQueue.empty(); +} + +TSQueue::t_Callable TSQueue::pop() +{ + std::lock_guard g(m); + auto f = std::move(futureQueue.front()); + futureQueue.pop_front(); + if (futureQueue.empty()) { + newData.Reset(); + } + return f; +} + +HANDLE TSQueue::getNewDataEventHandle() +{ + return newData.GetHandle(); +} diff --git a/tsqueue.h b/tsqueue.h new file mode 100644 index 0000000..70d7ca0 --- /dev/null +++ b/tsqueue.h @@ -0,0 +1,27 @@ +#ifndef TSQUEUE_H +#define TSQUEUE_H + +#include "Win32Event.h" +#include +#include +#include + +class TSQueue { +public: + using t_Callable = std::function; + + TSQueue(); + void add(t_Callable callable); + bool empty(); + t_Callable pop(); + + HANDLE getNewDataEventHandle(); +private: + using t_CallableQueue = std::deque; + + Win32Event newData; + std::mutex m; + t_CallableQueue futureQueue; +}; + +#endif // TSQUEUE_H diff --git a/win32event.cpp b/win32event.cpp new file mode 100644 index 0000000..3c3625d --- /dev/null +++ b/win32event.cpp @@ -0,0 +1 @@ +#include "win32event.h" diff --git a/win32event.h b/win32event.h new file mode 100644 index 0000000..77403e1 --- /dev/null +++ b/win32event.h @@ -0,0 +1,40 @@ +#ifndef WIN32EVENT_H +#define WIN32EVENT_H + + +#include +/** Simpel wrapper around a Win32 Event object. + +Mostly to make cleanup automatic.*/ +class Win32Event { +public: + enum class Reset { Auto=0, Manual=1 }; + enum class Initial { Clear=0, Set=1 }; + + Win32Event(Reset r, Initial is) + : hEvent(CreateEvent( + nullptr, // _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, + BOOL(r), // _In_ BOOL bManualReset, + BOOL(is), // _In_ BOOL bInitialState, + nullptr //_In_opt_ LPCTSTR lpName + )) + {} + + ~Win32Event() + { + CloseHandle(hEvent); + } + + Win32Event(const Win32Event &) = delete; + Win32Event &operator=(const Win32Event &) = delete; + + void Set() { SetEvent(hEvent); } + + void Reset() { ResetEvent(hEvent); } + + HANDLE GetHandle() { return hEvent; } +private: + HANDLE hEvent; +}; + +#endif // WIN32EVENT_H