From 38e20390ddf38edd74447ef7db2af660b8e0ff32 Mon Sep 17 00:00:00 2001 From: Niklas Halle Date: Mon, 24 Aug 2020 19:04:13 +0200 Subject: add all available commands (in this branch? @Max where are the others?) --- CMakeLists.txt | 18 +- include/Enum2String.hpp | 10 +- include/GetEssen.hpp | 47 +++++ include/Handler.hpp | 21 ++ include/RelationshipHandler.hpp | 17 ++ include/Response.hpp | 20 ++ include/SimpleHandlers.hpp | 13 ++ include/Utilities.hpp | 147 ++++++++++++++ include/sqdb.hpp | 227 +++++++++++++++++++++ src/GetEssen.cpp | 282 ++++++++++++++++++++++++++ src/Handler.cpp | 16 ++ src/RelationshipHandler.cpp | 88 ++++++++ src/Response.cpp | 36 ++++ src/SimpleHandlers.cpp | 66 ++++++ src/main.cpp | 173 ++-------------- src/sqdb.cpp | 437 ++++++++++++++++++++++++++++++++++++++++ 16 files changed, 1459 insertions(+), 159 deletions(-) create mode 100644 include/GetEssen.hpp create mode 100644 include/Handler.hpp create mode 100644 include/RelationshipHandler.hpp create mode 100644 include/Response.hpp create mode 100644 include/SimpleHandlers.hpp create mode 100644 include/Utilities.hpp create mode 100644 include/sqdb.hpp create mode 100644 src/GetEssen.cpp create mode 100644 src/Handler.cpp create mode 100644 src/RelationshipHandler.cpp create mode 100644 src/Response.cpp create mode 100644 src/SimpleHandlers.cpp create mode 100644 src/sqdb.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5bd1b0b..b68be2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,15 +13,29 @@ IF (CMAKE_VERSION VERSION_LESS "3.7.0") ENDIF () find_package(Boost REQUIRED COMPONENTS system) +find_package(Boost REQUIRED COMPONENTS date_time) add_executable(${PROJECT_NAME} # Headers - include/AnnotationTypes.hpp + include/sqdb.hpp + include/Handler.hpp + include/GetEssen.hpp + include/Response.hpp + include/Utilities.hpp include/Enum2String.hpp + include/SimpleHandlers.hpp + include/AnnotationTypes.hpp + include/RelationshipHandler.hpp # Sources src/main.cpp + src/sqdb.cpp + src/Handler.cpp + src/GetEssen.cpp + src/Response.cpp src/Enum2String.cpp + src/SimpleHandlers.cpp + src/RelationshipHandler.cpp ) target_include_directories(${PROJECT_NAME} @@ -39,6 +53,8 @@ target_link_directories(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE ${Boost_LIBRARIES} pthread + sqlite3 + curl ) #target_compile_definitions(${PROJECT_NAME} PRIVATE N_CORE) diff --git a/include/Enum2String.hpp b/include/Enum2String.hpp index 4992e0d..da3ab94 100644 --- a/include/Enum2String.hpp +++ b/include/Enum2String.hpp @@ -3,13 +3,13 @@ #undef END_ENUM #ifndef GENERATE_ENUM_STRINGS -#define DECL_ENUM_ELEMENT( element ) element -#define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME -#define END_ENUM( ENUM_NAME ) ENUM_NAME; \ +#define DECL_ENUM_ELEMENT(element) element +#define BEGIN_ENUM(ENUM_NAME) typedef enum tag##ENUM_NAME +#define END_ENUM(ENUM_NAME) ENUM_NAME; \ std::string GetString##ENUM_NAME(enum tag##ENUM_NAME index); #else #define DECL_ENUM_ELEMENT( element ) #element - #define BEGIN_ENUM( ENUM_NAME ) std::string gs_##ENUM_NAME [] = - #define END_ENUM( ENUM_NAME ) ; std::string GetString##ENUM_NAME(\ +#define BEGIN_ENUM( ENUM_NAME ) std::string gs_##ENUM_NAME [] = +#define END_ENUM( ENUM_NAME ) ; std::string GetString##ENUM_NAME(\ tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; } #endif \ No newline at end of file diff --git a/include/GetEssen.hpp b/include/GetEssen.hpp new file mode 100644 index 0000000..ff75029 --- /dev/null +++ b/include/GetEssen.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +#include "Handler.hpp" +#include "Response.hpp" + +namespace Handler { + namespace MensaHandler { + using std::string; + using boost::posix_time::ptime; + + struct MensaEssen { + int ID{191}; + string Message{"nothing got back! There was an error!!"}; + ptime Datum; + ptime LastModified; + }; + + enum mensa { + Adlershof = 191, Nord = 147, Sued = 367 + }; + + + bool db_update_mensa_message(const int &mensaID, const ptime &mensaDatum, const string &message); + + bool db_insert_mensa_message(const int &mensaID, const ptime &mensaDatum, const string &message); + + MensaEssen db_get_mensa_message(const int &mensaID, const ptime &mensaDatum); + + string getEssen(const int mensa_ID, const int offset); + + string read_mensa_message_from_file(const int &mensa_id_file); + } + + json mensaHandler(std::string const &arguments, std::string const &session, void *payload); +} + + + diff --git a/include/Handler.hpp b/include/Handler.hpp new file mode 100644 index 0000000..3d4b830 --- /dev/null +++ b/include/Handler.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "crow.h" + +namespace Handler { + typedef crow::json::wvalue json; + typedef std::function handler_function; + + struct CommandHandler { + [[nodiscard]] json exec(std::string const &arguments, std::string const &session) const { + return func(arguments, session, payload); + } + + handler_function func{nullptr}; + std::string description; + void *payload{nullptr}; + }; + + std::vector tokenizeArguments(std::string const &arguments, std::string const &delimiter = " "); +} \ No newline at end of file diff --git a/include/RelationshipHandler.hpp b/include/RelationshipHandler.hpp new file mode 100644 index 0000000..5b609f4 --- /dev/null +++ b/include/RelationshipHandler.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "Handler.hpp" + +namespace Handler { + namespace RelationShipHandler { + std::vector buildCalArr(std::vector nameV); + + int calculate(std::vector names); + + bool isAlphaNum(const std::string &str); + + std::string rsStart(std::vector const &names); + } + + json relationShipHandler(std::string const &arguments, std::string const &session, void *payload); +} diff --git a/include/Response.hpp b/include/Response.hpp new file mode 100644 index 0000000..53043c8 --- /dev/null +++ b/include/Response.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "crow.h" + +#include "AnnotationTypes.hpp" + +namespace Response { + using Reply::AnnotationType; + typedef crow::json::wvalue json; + + json simple_response(std::string const &text, std::string const &session = "null", bool success = true); + + json create_annotation(AnnotationType annotation = AnnotationType::none, std::string const &extra = ""); + + json create_text(std::string const &text); + + json create_text(std::string const &text, std::vector &&annotations); + + json create_response(std::vector &&reply, std::string const &session = "null", bool success = true); +} \ No newline at end of file diff --git a/include/SimpleHandlers.hpp b/include/SimpleHandlers.hpp new file mode 100644 index 0000000..06dfcd1 --- /dev/null +++ b/include/SimpleHandlers.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "Handler.hpp" + +namespace Handler { + json helpHandler(std::string const &arguments, std::string const &session, void *payload); + + json stopHandler(std::string const &arguments, std::string const &session, void *payload); + + json wikiHandler(std::string const &arguments, std::string const &session, void *payload); + + json klingerHandler(std::string const &arguments, std::string const &session, void *payload); +} \ No newline at end of file diff --git a/include/Utilities.hpp b/include/Utilities.hpp new file mode 100644 index 0000000..781b9fb --- /dev/null +++ b/include/Utilities.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include + +#define NOW std::time(0) +#define LOG_TIME_FORMAT "%y-%m-%d %H:%M:%S" +#define HR_TIME_FORMAT "%d.%m.%y, %H:%M:%S UHR" +#define NOW_STRING TimeStampToHReadble(NOW, false) + +static std::string TimeStampToHReadble(const time_t rawTime, bool HR_READABLE) { + struct tm *dt; + char buffer[30]; + dt = localtime(&rawTime); + if (HR_READABLE) { + strftime(buffer, sizeof(buffer), HR_TIME_FORMAT, dt); + } else { + strftime(buffer, sizeof(buffer), LOG_TIME_FORMAT, dt); + } + return std::string(buffer); +} + +#include + +static std::string getApiTokenFromFile(const std::string &filePath) { + std::string token = "NO-TOKEN"; + + std::ifstream inFile(filePath); + + if (inFile) { + // TODO: validate, at least a little - currently we just assume the file contains the correct data + std::getline(inFile, token); + } else { + return ""; + } + + return token; +} + +#ifdef NDEBUG + +static std::string getProductionApiToken() { + return getApiTokenFromFile("../release.api"); +} + +#else + +static std::string getDebugApiToken() { + return getApiTokenFromFile("../debug.api"); +} + +#endif + +static CURLcode downloadFile(const std::string &url, const std::string &fileName) { + + CURL *curl; + FILE *fp; + CURLcode res; + curl = curl_easy_init(); + + if (curl) { + fp = fopen(fileName.c_str(), "wb"); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36"); //so apparently the Website stw.berlin blocks the IPwhen accessing it very often with the bot... + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + //TODO: return int as status instead of boolean - maybe even this curl error? + return CURLE_COULDNT_CONNECT; + } + curl_easy_cleanup(curl); + fclose(fp); + return CURLE_OK; + } else { + return CURLE_COULDNT_CONNECT; + } +} + +static CURLcode downloadMensaFile(const int &ID, const boost::posix_time::ptime &datum) { + + std::string datumString = + std::to_string(datum.date().year()) + "-" + std::to_string(datum.date().month().as_number()) + "-" + + std::to_string(datum.date().day()); + + std::string postfield = "resources_id=" + std::to_string(ID) + "&date=" + datumString; + + CURL *curl; + FILE *fp; + CURLcode res; + curl = curl_easy_init(); + + if (curl) { + fp = fopen(std::to_string(ID).c_str(), "wb"); + + struct curl_slist *slist1; + + slist1 = nullptr; + slist1 = curl_slist_append(slist1, + "Cookie: seitenbereich=mensen; _ga=GA1.2.1719763776.1545837849; filterkennz=[]; PHPSESSID=9fa00137a2ad065fe595196ddb34e0ba; _gid=GA1.2.226483670.1549982576; loadDataForResource=191; storedPathname=/mensen.html; _gat=1"); + slist1 = curl_slist_append(slist1, "Origin: https://www.stw.berlin"); + slist1 = curl_slist_append(slist1, "Accept-Encoding: gzip, deflate, br"); + slist1 = curl_slist_append(slist1, "Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"); + slist1 = curl_slist_append(slist1, + "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"); + slist1 = curl_slist_append(slist1, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8"); + slist1 = curl_slist_append(slist1, "Accept: */*"); + slist1 = curl_slist_append(slist1, "Referer: https://www.stw.berlin/mensen.html"); + slist1 = curl_slist_append(slist1, "X-Requested-With: XMLHttpRequest"); + slist1 = curl_slist_append(slist1, "Connection: keep-alive"); + slist1 = curl_slist_append(slist1, "DNT: 1"); + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(curl, CURLOPT_URL, "https://www.stw.berlin/xhr/speiseplan-wochentag.html"); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfield.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) 32); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist1); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.63.0"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_2TLS); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_SSH_KNOWNHOSTS, "/home/max/.ssh/known_hosts"); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + //TODO: return int as status instead of boolean - maybe even this curl error? + return res; + } + curl_easy_cleanup(curl); + curl = nullptr; + curl_slist_free_all(slist1); + slist1 = nullptr; + fclose(fp); + return CURLE_OK; + } else { + return CURLE_FAILED_INIT; + } +} diff --git a/include/sqdb.hpp b/include/sqdb.hpp new file mode 100644 index 0000000..b92cac6 --- /dev/null +++ b/include/sqdb.hpp @@ -0,0 +1,227 @@ +#pragma once + +#include + +#include "sqlite3.h" + +#ifdef _WIN32 +# include +# define SQDB_MAKE_TEXT(x) _TEXT(x) +# define SQDB_STRLEN _tcslen +# define SQDB_STRDUP _tcsdup +#else +# define SQDB_MAKE_TEXT(x) (x) +# define SQDB_STRLEN strlen +# define SQDB_STRDUP strdup +#endif + +#if !defined(SQDB_UTF16) && !defined(SQDB_UTF8) +# ifdef _WIN32 +# if defined(UNICODE) || defined(_UNICODE) +# define SQDB_UTF16 +# else +# define SQDB_UTF8 +# endif +# else +# define SQDB_UTF8 +# endif +#endif + +#ifdef SQDB_UTF8 +# define SQDB_CHAR char +# define SQDB_STD_STRING std::string +#endif + +#ifdef SQDB_UTF16 +# define SQDB_CHAR TCHAR +# define SQDB_STD_STRING std::wstring +#endif + +namespace sqdb { + + class Exception { + public: + Exception(sqlite3 *db); + + Exception(sqlite3 *db, int errorCode); + + Exception(const SQDB_CHAR *errorMsg); + + ~Exception(); + + int GetErrorCode() const; + + const SQDB_CHAR *GetErrorMsg() const; + + private: + int m_errorCode; + SQDB_CHAR *m_errorMsg; + }; + +#define CHECK(db, returnCode) \ + if ( (returnCode) != SQLITE_OK ) throw Exception(db, returnCode) + + class RefCount { + protected: + RefCount(); + + RefCount(const RefCount &x); + + RefCount &operator=(const RefCount &x); + + void IncRef(); + + unsigned DecRef(); + + private: + unsigned *m_refCount; + }; + + class Blob : public RefCount { + public: + Blob(const void *data, int size); + + Blob(const Blob &x); + + Blob &operator=(const Blob &x); + + int GetSize() const; + + const char *GetData() const; + + ~Blob(); + + private: + char *m_data; + int m_size; + }; + + class Convertor { + public: + Convertor(sqlite3 *db, sqlite3_stmt *stmt, int field); + + operator int() const; + + operator unsigned long() const; + + operator long long() const; + + operator double() const; + + operator SQDB_STD_STRING() const; + + operator const SQDB_CHAR *() const; + + operator Blob() const; + + int GetInt() const; + + unsigned long GetUnsignedLong() const; + + long long GetLongLong() const; + + double GetDouble() const; + + SQDB_STD_STRING GetString() const; + + const SQDB_CHAR *GetText() const; + + Blob GetBlob() const; + + private: + sqlite3 *m_db; + sqlite3_stmt *m_stmt; + int m_field; + }; + + class Statement : public RefCount { + public: + Statement(sqlite3 *db, sqlite3_stmt *stmt); + + Statement(const Statement &x); + + Statement &operator=(const Statement &x); + + bool Next(); + + Convertor GetField(int field) const; + + template + void Bind(int i, const T &value) { + if (m_needReset) + Reset(); + DoBind(i, value); + } + + void BindBlob(int i, const void *value, int n); + + void BindNull(int i); + + ~Statement(); + + private: + void DoBind(int i, int value); + + void DoBind(int i, long long value); + + void DoBind(int i, double value); + + void DoBind(int i, const SQDB_STD_STRING &value); + + void DoBind(int i, const SQDB_CHAR *value); + + // Bind blob. + void DoBind(int i, const void *value, int n); + + // Bind null. + void DoBind(int i); + + // Reset binders so that new values can be bound. + void Reset(); + + sqlite3 *m_db; + sqlite3_stmt *m_stmt; + bool m_needReset; + }; + + class QueryStr { + public: + QueryStr(); + + const SQDB_CHAR *Format(const SQDB_CHAR *fmt, ...); + + const SQDB_CHAR *Get() const; + + ~QueryStr(); + + private: + SQDB_CHAR *m_buf; + }; + + class Db : public RefCount { + public: + Db(const SQDB_CHAR *fileName); + + void BeginTransaction(); + + void CommitTransaction(); + + void RollbackTransaction(); + + bool TableExists(const SQDB_CHAR *tableName); + + Statement Query(const SQDB_CHAR *queryStr); + + long long LastId(); + + Db(const Db &x); + + Db &operator=(const Db &x); + + ~Db(); + + private: + sqlite3 *m_db; + }; + +} diff --git a/src/GetEssen.cpp b/src/GetEssen.cpp new file mode 100644 index 0000000..3825c5c --- /dev/null +++ b/src/GetEssen.cpp @@ -0,0 +1,282 @@ +#include "GetEssen.hpp" + +#include "sqdb.hpp" +#include "Utilities.hpp" + +using std::string; + +bool +Handler::MensaHandler::db_update_mensa_message(const int &mensaID, const ptime &mensaDatum, const string &message) { + sqdb::Db db("../main.sqlite"); + string newTime = to_simple_string(ptime(boost::posix_time::second_clock::local_time())); + string mensaDatumString = + std::to_string(mensaDatum.date().year()) + "-" + mensaDatum.date().month().as_short_string() + "-" + + std::to_string(mensaDatum.date().day().as_number()); + sqdb::QueryStr str; + db.Query(str.Format( + SQDB_MAKE_TEXT("update mensa set Message = '%s', LastModified = '%s' where ID = %i and Datum = '%s';"), + message.c_str(), newTime.c_str(), mensaID, mensaDatumString.c_str())).Next(); + return true; +} + +bool +Handler::MensaHandler::db_insert_mensa_message(const int &mensaID, const ptime &mensaDatum, const string &message) { + sqdb::Db db("../main.sqlite"); + string newTime = to_simple_string(ptime(boost::posix_time::second_clock::local_time())); + string mensaDatumString = + std::to_string(mensaDatum.date().year()) + "-" + mensaDatum.date().month().as_short_string() + "-" + + std::to_string(mensaDatum.date().day().as_number()); + sqdb::QueryStr str; + db.Query(str.Format(SQDB_MAKE_TEXT("insert into mensa values(%i, '%s', '%s', '%s');"), mensaID, message.c_str(), + mensaDatumString.c_str(), newTime.c_str())).Next(); + return true; +} + +Handler::MensaHandler::MensaEssen +Handler::MensaHandler::db_get_mensa_message(const int &mensaID, const ptime &mensaDatum) { + MensaEssen out; + sqdb::Db db("../main.sqlite"); + + sqdb::QueryStr str; + string mensaDatumString = + std::to_string(mensaDatum.date().year()) + "-" + mensaDatum.date().month().as_short_string() + "-" + + std::to_string(mensaDatum.date().day().as_number()); + sqdb::Statement s = db.Query( + str.Format(SQDB_MAKE_TEXT("SELECT * from mensa WHERE Datum = '%s' AND ID = %i;"), mensaDatumString.c_str(), + mensaID)); + + try { + if (s.Next()) { + int ID = s.GetField(0); + string Message = s.GetField(1); + string Datum = s.GetField(2); + string LastModified = s.GetField(3); + + + ptime LMDate(boost::posix_time::time_from_string(LastModified)); + + out.ID = ID; + out.Message = Message; + out.Datum = mensaDatum; + out.LastModified = LMDate; + return out; + } + } catch (sqdb::Exception &exception) { + std::cerr << "Error code: " << exception.GetErrorCode() << " Error Message: " << exception.GetErrorMsg() + << std::endl; + } + out.ID = -2; + return out; + +} + +string Handler::MensaHandler::getEssen(const int mensa_ID, const int offset) { + MensaEssen Essen; + if (offset >= 0) { + + if (offset > 28) { + return "Ich fühle mich geschmeichelt, dass du denkst, dass ich so weit in die Zukunft gucken kann (0 bis 28 Tage)"; + } + + ptime datumNow = boost::posix_time::second_clock::local_time(); + ptime datumEssen = datumNow + (boost::gregorian::days(offset)); + + Essen = db_get_mensa_message(mensa_ID, datumEssen); + if (Essen.ID == -2) { // SQL got nothing back :/ + //log("sql got nothing back!"); + if (downloadMensaFile(mensa_ID, datumEssen) != CURLE_OK) { + std::cerr << "Error downloading file" << std::endl; + return "Error downloading file!"; + } + db_insert_mensa_message(mensa_ID, datumEssen, read_mensa_message_from_file(mensa_ID)); + Essen = db_get_mensa_message(mensa_ID, datumEssen); + + string out; + switch (Essen.ID) { + case mensa::Adlershof : + out = "Mensa Adlershof am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + ":"; + break; + case mensa::Nord: + out = "Mensa Nord am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + ":"; + break; + case mensa::Sued : + out = "Mensa Sued am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + ":"; + break; + default: + out = "Mensa Unbekannt am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + ":"; + break; + + } + out += Essen.Message; + out += "\n (Stand: " + to_simple_string(Essen.LastModified) + ")\n"; + + return out; + } + + if (Essen.LastModified + boost::posix_time::hours(2) < datumNow) { //LastModified is more than 2 hours old + if (downloadMensaFile(mensa_ID, datumEssen) != CURLE_OK) { + return "Error downloading file!"; + } + + db_update_mensa_message(mensa_ID, datumEssen, read_mensa_message_from_file(mensa_ID)); + Essen = db_get_mensa_message(mensa_ID, datumEssen); + } + string out; + switch (Essen.ID) { + case mensa::Adlershof : + out = "Mensa Adlershof am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + + ":"; + break; + case mensa::Nord : + out = "Mensa Nord am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + + ":"; + break; + case mensa::Sued : + out = "Mensa Sued am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + + ":"; + break; + default: + out = "Mensa Unbekannt am " + std::to_string(Essen.Datum.date().day().as_number()) + "." + + std::to_string(Essen.Datum.date().month().as_number()) + "." + + std::to_string(Essen.Datum.date().year()) + + ":"; + break; + + } + out += Essen.Message; + out += "\n (Stand: " + to_simple_string(Essen.LastModified) + ")\n"; + return out; + } + + return "Wer denkst du bin ich? Ein Zeitreisender?\nIch kann zwar in die Zukunft gucken, aber nur 28 Tage"; +} + +string Handler::MensaHandler::read_mensa_message_from_file(const int &mensa_id_file) { + string s; + string out; + bool serious = false; + + std::ifstream inFile(std::to_string(mensa_id_file)); + while (std::getline(inFile, s)) { + //out += mensa; + if (s.find("15.png") != string::npos && serious) { + string vegan; + + int vegan_byte[4] = {0xF0, 0x9F, 0x8C, 0xB1}; + for (int i : vegan_byte) { + vegan += (char) i; + } + + out += vegan; + } + + if (s.find("1.png") != string::npos && serious) { + string vegetarian; + + int vegetarian_byte[4] = {0xF0, 0x9F, 0x8C, 0xBD}; + for (int i : vegetarian_byte) { + vegetarian += (char) i; + } + + out += vegetarian; + } + if (s.find("splIcon") != string::npos && serious && s.find("1.png") == string::npos && + s.find("15.png") == string::npos) { + string emptyEmoji; + int emptyEmoji_byte[6] = {0xE2, 0x98, 0xA3, 0xEF, 0xB8, 0x8F}; + for (int i : emptyEmoji_byte) { + emptyEmoji += (char) i; + } + + out += emptyEmoji; + } + + if (s.find("splGroup\"") != string::npos) { //Getting the Category of the Meal + for (int i = 0, j = 0; (unsigned) i < s.length(); ++i) { + if (s.at(i) == '>') { + j = i + 1; + } + if (s.at(i) == '<' && j != 0) { + s = s.substr(j, i - j); + j = 0; + } + + } + serious = (s == "Aktionen" || s == "Essen" || s == "Beilagen"); + if (serious) { + out += "\n" + s + "\n"; + } + } + if (s.find("class=\"bold\"") != string::npos && serious) { //Getting the Meal + for (int i = 0, j = 0; (unsigned) i < s.length(); ++i) { + if (s.at(i) == '>') { + j = i + 1; + //logStatus("Zeichen > gefunden in Position" + std::std::to_string(i)); + } + if (s.at(i) == '<' && j != 0) { + s = s.substr(j, i - j); + j = 0; + } + + } + + out += "\t" + s; + } + + if (s.find("euro;") != string::npos && serious) { //geting the price of each Meal + for (int i = 0, j = 0; (unsigned) i < s.length(); ++i) { + if (s.at(i) == ';') { + j = i + 1; + } + if (s.at(i) == '/') { + s = s.substr(j, i - j); + j = 0; + } + } + out += "\t" + s + "€\n"; + } + } + + if (out.empty()) { + return "Heute kein Essen"; + } + return out; + +} + +Handler::json Handler::mensaHandler(const std::string &arguments, const std::string &session, void *payload) { + Handler::MensaHandler::mensa Mensa_ID; + + auto args = tokenizeArguments(arguments); + + std::string mensa_name = (args.size() > 0 ? args.at(0) : "adlershof"); + std::string offset_request = (args.size() > 1 ? args.at(1) : "0"); + + if (mensa_name == "adlershof") { + Mensa_ID = Handler::MensaHandler::mensa::Adlershof; + } else if (mensa_name == "nord") { + Mensa_ID = Handler::MensaHandler::mensa::Nord; + } else if (mensa_name == "sued" || mensa_name == "süd") { + Mensa_ID = Handler::MensaHandler::mensa::Sued; + } else { + Mensa_ID = Handler::MensaHandler::mensa::Adlershof; + } + + int offset = std::stoi(offset_request); + + return Response::simple_response(getEssen(Mensa_ID, offset)); +} diff --git a/src/Handler.cpp b/src/Handler.cpp new file mode 100644 index 0000000..1c0cd12 --- /dev/null +++ b/src/Handler.cpp @@ -0,0 +1,16 @@ +#include "Handler.hpp" + +std::vector Handler::tokenizeArguments(const std::string &arguments, const std::string &delimiter) { + std::vector tokens{}; + + auto start = 0U; + auto end = arguments.find(delimiter); + while (end != std::string::npos) { + tokens.emplace_back(arguments.substr(start, end - start)); + start = end + delimiter.length(); + end = arguments.find(delimiter, start); + } + tokens.emplace_back(arguments.substr(start, end)); + + return tokens; +} diff --git a/src/RelationshipHandler.cpp b/src/RelationshipHandler.cpp new file mode 100644 index 0000000..5aab81b --- /dev/null +++ b/src/RelationshipHandler.cpp @@ -0,0 +1,88 @@ +#include "Response.hpp" +#include "RelationshipHandler.hpp" + +using std::string; +using std::vector; + +vector Handler::RelationShipHandler::buildCalArr(vector nameV) { + string ges = nameV.at(0) + nameV.at(1); + + for (auto &c : ges) { + c = tolower(c); + } + + vector v{}; + for (auto c1 : ges) { + int count = 0; + for (auto c2: ges) { + if (c1 == c2) { + ++count; + } + } + v.push_back(count); + } + + return v; +} + +int Handler::RelationShipHandler::calculate(vector names) { + sort(names.begin(), names.end()); + vector nums = buildCalArr(names); + + while (nums.size() > 2) { + vector tempNums{}; + for (std::size_t i = 0; i < nums.size() / 2; i++) { + int temp = nums.at(i) + nums.at(nums.size() - i - 1); + if (nums.size() % 2 == 1 && i == nums.size() / 2) { + tempNums.push_back(nums.at(i)); + } else { + if (temp < 10) { + tempNums.push_back(temp); + } else { + tempNums.push_back(temp / 10); + tempNums.push_back(temp % 10); + } + } + } + if (nums.size() % 2 == 1) { + tempNums.push_back(nums.at(nums.size() / 2)); + } + nums.clear(); + for (auto i: tempNums) { + nums.push_back(i); + } + } + + return 10 * nums.at(0) + nums.at(1); +} + +bool Handler::RelationShipHandler::isAlphaNum(const string &str) { + for (char i: str) { + if (!isalnum(i)) { + return false; + } + } + return true; +} + +string Handler::RelationShipHandler::rsStart(vector const &names) { + //vector names = removeDupWord(str); + if (names.size() != 2) { + return "Gib zwei Namen ein Du Troll!"; + } + if (!isAlphaNum(names.at(0))) { + return "Seit wann enthælt ein Name Sonderzeichen oder Zahlen? Spüre den Zorn von N!"; + } + if (!isAlphaNum(names.at(1))) { + return "Seit wann enthælt ein Name Sonderzeichen oder Zahlen? Spüre den Zorn von N!"; + } + + int result = calculate(names); + return (names.at(0) + " und " + names.at(1) + " passen nach Angaben von N zu " + std::to_string(result) + + "% zusammen. Gratuliere!\n"); +} + +Handler::json Handler::relationShipHandler(const string &arguments, const string &session, void *payload) { + (void) payload; + return Response::simple_response(RelationShipHandler::rsStart(tokenizeArguments(arguments))); +} diff --git a/src/Response.cpp b/src/Response.cpp new file mode 100644 index 0000000..050a537 --- /dev/null +++ b/src/Response.cpp @@ -0,0 +1,36 @@ +#include "Response.hpp" + +Response::json Response::simple_response(std::string const &text, std::string const &session, bool success) { + std::vector reply_vec; + reply_vec.emplace_back(create_text(text)); + + return create_response(std::move(reply_vec), session, success); +} + +Response::json Response::create_annotation(Reply::AnnotationType annotationType, std::string const &extra) { + json annotation(crow::json::type::Object); + annotation["type"] = Reply::GetStringAnnotationType(annotationType); + annotation["extra"] = extra; + return annotation; +} + +Response::json Response::create_text(std::string const &text) { + std::vector annotations; + annotations.emplace_back(create_annotation()); + return create_text(text, std::move(annotations)); +} + +Response::json Response::create_text(std::string const &text, std::vector &&annotations) { + json reply(crow::json::type::Object); + reply["text"] = text; + reply["annotations"] = std::move(annotations); + return reply; +} + +Response::json Response::create_response(std::vector &&reply, std::string const &session, bool success) { + json response(crow::json::type::Object); + response["success"] = success; + response["session"] = session; + response["reply"] = std::move(reply); + return response; +} \ No newline at end of file diff --git a/src/SimpleHandlers.cpp b/src/SimpleHandlers.cpp new file mode 100644 index 0000000..033da53 --- /dev/null +++ b/src/SimpleHandlers.cpp @@ -0,0 +1,66 @@ +#include "SimpleHandlers.hpp" +#include "Response.hpp" + +using namespace Response; + +Handler::json Handler::klingerHandler(std::string const &arguments, std::string const &session, void *payload) { + (void) payload; + return simple_response("Bonjour!"); +} + +Handler::json Handler::wikiHandler(std::string const &arguments, std::string const &session, void *payload) { + (void) payload; + + std::vector linkAnnotations; + linkAnnotations.emplace_back(create_annotation(Reply::AnnotationType::link, "Wikipedia")); + linkAnnotations.emplace_back(create_annotation(Reply::AnnotationType::underline)); + + std::vector reply_vec; + reply_vec.emplace_back(create_text("Visit ")); + reply_vec.emplace_back(create_text("https://eo.wikipedia.org/", std::move(linkAnnotations))); + reply_vec.emplace_back(create_text(" today!")); + + return create_response(std::move(reply_vec), session, true); +} + +Handler::json Handler::helpHandler(std::string const &arguments, std::string const &session, void *payload) { + auto commands = *(std::unordered_map *) payload; + + std::vector reply_vec; + reply_vec.emplace_back(create_text( + "Reading from the gospel according to saint N,\n" + "And you shall feast upon the fruits and vegetables as I do in the offering for they will make you strong.\n" + "\n" + "Dear my fellow users, these are my official commands, which are offered to my believers.\n" + "\n")); + + for (auto const &itor : commands) { + std::vector commandAnnotations; + commandAnnotations.emplace_back(create_annotation(Reply::AnnotationType::command)); + + reply_vec.emplace_back(create_text("- ")); + reply_vec.emplace_back(create_text(itor.first, std::move(commandAnnotations))); + reply_vec.emplace_back(create_text(" " + itor.second.description + "\n")); + } + + reply_vec.emplace_back(create_text( + "\n" + "Thanks for using me. Takk skal du ha.\n" + "Bussy\n" + "N")); + + return create_response(std::move(reply_vec), session, true); +} + +Handler::json Handler::stopHandler(std::string const &arguments, std::string const &session, void *payload) { + auto app = (crow::SimpleApp *) payload; + app->stop(); + + std::vector annotations; + annotations.emplace_back(create_annotation(Reply::AnnotationType::bold)); + + std::vector reply_vec; + reply_vec.emplace_back(create_text("stopped", std::move(annotations))); + + return create_response(std::move(reply_vec), session, true); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6e39f1c..72fd9a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,63 +3,32 @@ #include #include "crow.h" +#include "sqdb.hpp" -#include "AnnotationTypes.hpp" +#include "Handler.hpp" +#include "Response.hpp" -#define NULL_STRING std::string("null") -#define EMPTY_STRING std::string("") -#define NO_SUCH_COMMAND std::string("no_such_command") +/* BEGIN Handlers */ +#include "GetEssen.hpp" +#include "SimpleHandlers.hpp" +#include "RelationshipHandler.hpp" -typedef crow::json::wvalue json; -typedef std::function handler_function; - -struct CommandHandler { - [[nodiscard]] json exec(std::string const &arguments, std::string const &session) const { - return func(arguments, session, payload); - } - - handler_function func{nullptr}; - std::string description; - void *payload{nullptr}; -}; - -/* ------------------------------------------------------------------------------------------------------------------ */ - -json helpHandler(std::string const &arguments, std::string const &session, void *payload); - -json stopHandler(std::string const &arguments, std::string const &session, void *payload); - -json wikiHandler(std::string const &arguments, std::string const &session, void *payload); - -json klingerHandler(std::string const &arguments, std::string const &session, void *payload); - -/* ------------------------------------------------------------------------------------------------------------------ */ - -json simple_response(std::string const &text, std::string const &session = NULL_STRING, bool success = true); - -json create_annotation(Reply::AnnotationType annotation = Reply::AnnotationType::none, - std::string const &extra = EMPTY_STRING); - -json create_text(std::string const &text); - -json create_text(std::string const &text, std::vector &&annotations); - -json create_response(std::vector &&reply, std::string const &session = NULL_STRING, bool success = true); - -/* ------------------------------------------------------------------------------------------------------------------ */ +/* END Handlers */ int main() { crow::SimpleApp app; // command --> handler - std::unordered_map commands{ - {"stop", {.func = stopHandler, .description = "Stops the bot", .payload = &app}}, - {"wiki", {.func = wikiHandler, .description = "Send you to wikipedia"}}, - {"klinger", {.func = klingerHandler, .description = "Greats in french. Bonjour!"}}, + std::unordered_map commands{ + {"wiki", {.func = Handler::wikiHandler, .description = "Send you to wikipedia"}}, + {"stop", {.func = Handler::stopHandler, .description = "Stops the bot", .payload = &app}}, + {"mensa", {.func = Handler::mensaHandler, .description = "{adlershof, nord, sued} Shows the daily menu of the mensa at various places."}}, + {"klinger", {.func = Handler::klingerHandler, .description = "Greats in french. Bonjour!"}}, + {"relation", {.func = Handler::relationShipHandler, .description = "[name1] [name2] Shows the result of an odd astrological religious pseudo-algorithm, based on the fact how much blessing a relationship receives by the glorious N."}}, }; commands.insert({"help", - {.func = helpHandler, .description = "This is my holy manual.", .payload = &commands}}); + {.func = Handler::helpHandler, .description = "This is my holy manual.", .payload = &commands}}); CROW_ROUTE(app, "/") .methods("POST"_method) @@ -72,122 +41,20 @@ int main() { if (!data.count("command")) return crow::response(400, "malformed request: missing `command` field\n"); - std::string command = (data.count("command") ? data["command"].s() : NO_SUCH_COMMAND); - std::string arguments = (data.count("arguments") ? data["arguments"].s() : EMPTY_STRING); - std::string session = (data.count("session") ? data["session"].s() : NULL_STRING); + std::string command = (data.count("command") ? data["command"].s() : std::string( + "No such command!")); + std::string arguments = (data.count("arguments") ? data["arguments"].s() : std::string("")); + std::string session = (data.count("session") ? data["session"].s() : std::string("null")); if (auto itor = commands.find(command); itor != commands.end()) { auto handler = itor->second; return crow::response{handler.exec(arguments, session)}; } - return crow::response{simple_response("No such command!", NULL_STRING, false)}; + return crow::response{Response::simple_response("No such command!", "null", false)}; }); app.port(18080).multithreaded().run(); std::cout << "Stopped successfully" << std::endl; } - -/* ------------------------------------------------------------------------------------------------------------------ */ - -json klingerHandler(std::string const &arguments, std::string const &session, void *payload) { - (void) payload; - return simple_response("Bonjour!"); -} - -json wikiHandler(std::string const &arguments, std::string const &session, void *payload) { - (void) payload; - - std::vector linkAnnotations; - linkAnnotations.emplace_back(create_annotation(Reply::AnnotationType::link, "Wikipedia")); - linkAnnotations.emplace_back(create_annotation(Reply::AnnotationType::underline)); - - std::vector reply_vec; - reply_vec.emplace_back(create_text("Visit ")); - reply_vec.emplace_back(create_text("https://eo.wikipedia.org/", std::move(linkAnnotations))); - reply_vec.emplace_back(create_text(" today!")); - - return create_response(std::move(reply_vec), session, true); -} - -json helpHandler(std::string const &arguments, std::string const &session, void *payload) { - auto commands = *(std::unordered_map *) payload; - - std::vector reply_vec; - reply_vec.emplace_back(create_text( - "Reading from the gospel according to saint N,\n" - "And you shall feast upon the fruits and vegetables as I do in the offering for they will make you strong.\n" - "\n" - "Dear my fellow users, these are my official commands, which are offered to my believers.\n" - "\n")); - - for (auto const &itor : commands) { - std::vector commandAnnotations; - commandAnnotations.emplace_back(create_annotation(Reply::AnnotationType::command)); - - reply_vec.emplace_back(create_text("- ")); - reply_vec.emplace_back(create_text(itor.first, std::move(commandAnnotations))); - reply_vec.emplace_back(create_text(" " + itor.second.description + "\n")); - } - - reply_vec.emplace_back(create_text( - "\n" - "Thanks for using me. Takk skal du ha.\n" - "Bussy\n" - "N")); - - return create_response(std::move(reply_vec), session, true); -} - -json stopHandler(std::string const &arguments, std::string const &session, void *payload) { - auto app = (crow::SimpleApp *) payload; - app->stop(); - - std::vector annotations; - annotations.emplace_back(create_annotation(Reply::AnnotationType::bold)); - - std::vector reply_vec; - reply_vec.emplace_back(create_text("stopped", std::move(annotations))); - - return create_response(std::move(reply_vec), session, true); -} - -/* ------------------------------------------------------------------------------------------------------------------ */ - -json simple_response(std::string const &text, std::string const &session, bool success) { - std::vector reply_vec; - reply_vec.emplace_back(create_text(text)); - - return create_response(std::move(reply_vec), session, success); -} - -json create_annotation(Reply::AnnotationType annotationType, std::string const &extra) { - json annotation(crow::json::type::Object); - annotation["type"] = Reply::GetStringAnnotationType(annotationType); - annotation["extra"] = extra; - return annotation; -} - -json create_text(std::string const &text) { - std::vector annotations; - annotations.emplace_back(create_annotation()); - return create_text(text, std::move(annotations)); -} - -json create_text(std::string const &text, std::vector &&annotations) { - json reply(crow::json::type::Object); - reply["text"] = text; - reply["annotations"] = std::move(annotations); - return reply; -} - -json create_response(std::vector &&reply, std::string const &session, bool success) { - json response(crow::json::type::Object); - response["success"] = success; - response["session"] = session; - response["reply"] = std::move(reply); - return response; -} - -// curl -d '{"command":"klinger", "b":"12"}' -H "Content-Type: application/json" -X POST http://localhost:18080/ diff --git a/src/sqdb.cpp b/src/sqdb.cpp new file mode 100644 index 0000000..4a04f05 --- /dev/null +++ b/src/sqdb.cpp @@ -0,0 +1,437 @@ +#include +#include +#include +#include + +#include + +#include "sqdb.hpp" + +#include + +using namespace sqdb; + +Exception::Exception(sqlite3 *db) { + m_errorCode = sqlite3_errcode(db); + auto *c = (SQDB_CHAR *) +#ifdef SQDB_UTF8 + sqlite3_errmsg +#else + sqlite3_errmsg16 +#endif + (db); + m_errorMsg = SQDB_STRDUP(c); +} + +Exception::Exception(sqlite3 *db, int errorCode) + : m_errorCode(errorCode) { + auto *c = (SQDB_CHAR *) +#ifdef SQDB_UTF8 + sqlite3_errmsg +#else + sqlite3_errmsg16 +#endif + (db); + m_errorMsg = SQDB_STRDUP(c); +} + +Exception::Exception(const SQDB_CHAR *errorMsg) + : m_errorCode(-1) { + m_errorMsg = SQDB_STRDUP(errorMsg); +} + +Exception::~Exception() { + free(m_errorMsg); +} + +int Exception::GetErrorCode() const { + return m_errorCode; +} + +const SQDB_CHAR *Exception::GetErrorMsg() const { + return m_errorMsg; +} + +RefCount::RefCount() + : m_refCount(nullptr) { +} + +RefCount::RefCount(const RefCount &x) + : m_refCount(x.m_refCount) { +} + +RefCount &RefCount::operator=(const RefCount &x) { + if (this != &x) { + m_refCount = x.m_refCount; + } + return *this; +} + +void RefCount::IncRef() { + if (!m_refCount) { + m_refCount = new unsigned; + *m_refCount = 0; + } + ++*m_refCount; +} + +unsigned RefCount::DecRef() { + assert(m_refCount); + unsigned value = --*m_refCount; + if (value == 0) { + delete m_refCount; + m_refCount = nullptr; + } + return value; +} + +Blob::Blob(const void *data, int size) + : m_size(size) { + m_data = new char[size]; + std::uninitialized_copy((char *) data, (char *) data + size, m_data); + IncRef(); +} + +Blob::Blob(const Blob &x) + : RefCount(x), m_data(x.m_data), m_size(x.m_size) { + IncRef(); +} + +Blob &Blob::operator=(const Blob &x) { + if (this != &x) { + RefCount::operator=(x); + IncRef(); + m_data = x.m_data; + m_size = x.m_size; + } + return *this; +} + +int Blob::GetSize() const { + return m_size; +} + +const char *Blob::GetData() const { + return m_data; +} + +Blob::~Blob() { + if (DecRef() == 0) { + delete[] m_data; + } +} + +Convertor::Convertor(sqlite3 *db, sqlite3_stmt *stmt, int field) + : m_db(db), m_stmt(stmt), m_field(field) { +} + +Convertor::operator int() const { + return GetInt(); +} + +Convertor::operator unsigned long() const { + return GetUnsignedLong(); +} + +Convertor::operator long long() const { + return GetLongLong(); +} + +Convertor::operator double() const { + return GetDouble(); +} + +Convertor::operator SQDB_STD_STRING() const { + return GetString(); +} + +Convertor::operator const SQDB_CHAR *() const { + return GetText(); +} + +Convertor::operator Blob() const { + return GetBlob(); +} + +int Convertor::GetInt() const { + assert(m_stmt); + return sqlite3_column_int(m_stmt, m_field); +} + +unsigned long Convertor::GetUnsignedLong() const { + assert(m_stmt); + const char *data = (char *) sqlite3_column_blob(m_stmt, m_field); + return strtoul(data, nullptr, 10); +} + +long long Convertor::GetLongLong() const { + assert(m_stmt); + return sqlite3_column_int64(m_stmt, m_field); +} + +double Convertor::GetDouble() const { + assert(m_stmt); + return sqlite3_column_double(m_stmt, m_field); +} + +SQDB_STD_STRING Convertor::GetString() const { + assert(m_stmt); + const auto *result = (const SQDB_CHAR *) +#ifdef SQDB_UTF8 + sqlite3_column_text +#else + sqlite3_column_text16 +#endif + (m_stmt, m_field); + return result; +} + +const SQDB_CHAR *Convertor::GetText() const { + assert(m_stmt); + const auto *result = (const SQDB_CHAR *) +#ifdef SQDB_UTF8 + sqlite3_column_text +#else + sqlite3_column_text16 +#endif + (m_stmt, m_field); + return result; +} + +Blob Convertor::GetBlob() const { + assert(m_stmt); + const void *data = sqlite3_column_blob(m_stmt, m_field); + int size = sqlite3_column_bytes(m_stmt, m_field); + return Blob(data, size); +} + +Statement::Statement(sqlite3 *db, sqlite3_stmt *stmt) + : RefCount(), m_db(db), m_stmt(stmt), m_needReset(false) { + IncRef(); +} + +Statement::Statement(const Statement &x) + : RefCount(x), m_db(x.m_db), m_stmt(x.m_stmt), m_needReset(false) { + IncRef(); +} + +Statement &Statement::operator=(const Statement &x) { + if (this != &x) { + RefCount::operator=(x); + IncRef(); + m_db = x.m_db; + m_stmt = x.m_stmt; + m_needReset = x.m_needReset; + } + return *this; +} + +bool Statement::Next() { + assert(m_stmt); + int ret = SQLITE_BUSY; + + while (ret == SQLITE_BUSY) { + ret = sqlite3_step(m_stmt); + m_needReset = true; + if (ret == SQLITE_DONE) { + return false; + } else if (ret == SQLITE_ROW) { + return true; + } else if (ret == SQLITE_BUSY) { + sleep(1); + continue; + } else { + throw Exception(m_db, ret); + } + } + return false; +} + +Convertor Statement::GetField(int field) const { + return {m_db, m_stmt, field}; +} + +Statement::~Statement() { + if (DecRef() == 0) { + sqlite3_finalize(m_stmt); + } +} + +void Statement::BindBlob(int i, const void *value, int n) { + if (m_needReset) + Reset(); + DoBind(i, value, n); +} + +void Statement::BindNull(int i) { + if (m_needReset) + Reset(); + DoBind(i); +} + +void Statement::DoBind(int i, int value) { + const int ret = sqlite3_bind_int(m_stmt, i, value); + CHECK(m_db, ret); +} + +void Statement::DoBind(int i, long long value) { + const int ret = sqlite3_bind_int64(m_stmt, i, value); + CHECK(m_db, ret); +} + +void Statement::DoBind(int i, double value) { + const int ret = sqlite3_bind_double(m_stmt, i, value); + CHECK(m_db, ret); +} + +void Statement::DoBind(int i, const SQDB_STD_STRING &value) { + const int ret = +#ifdef SQDB_UTF8 + sqlite3_bind_text +#else + sqlite3_bind_text16 +#endif + (m_stmt, i, value.c_str(), value.size() * sizeof(SQDB_STD_STRING::value_type), SQLITE_TRANSIENT); + CHECK(m_db, ret); +} + +void Statement::DoBind(int i, const SQDB_CHAR *value) { + const int len = SQDB_STRLEN(value); + + const int ret = +#ifdef SQDB_UTF8 + sqlite3_bind_text +#else + sqlite3_bind_text16 +#endif + (m_stmt, i, value, len * sizeof(SQDB_CHAR), SQLITE_TRANSIENT); + + CHECK(m_db, ret); +} + +void Statement::DoBind(int i, const void *value, int n) { + const int ret = sqlite3_bind_blob(m_stmt, i, value, n, SQLITE_TRANSIENT); + CHECK(m_db, ret); +} + +void Statement::DoBind(int i) { + const int ret = sqlite3_bind_null(m_stmt, i); + CHECK(m_db, ret); +} + +void Statement::Reset() { + assert(m_needReset); + assert(m_stmt); + + sqlite3_reset(m_stmt); + m_needReset = false; +} + +QueryStr::QueryStr() + : m_buf(nullptr) { +} + +const SQDB_CHAR *QueryStr::Format(const SQDB_CHAR *fmt, ...) { + va_list va; + va_start(va, fmt); +#ifdef SQDB_UTF8 + sqlite3_free(m_buf); + m_buf = sqlite3_vmprintf(fmt, va); +#else + free(m_buf); + int len = _vscwprintf(fmt, va) + 1; + m_buf = (SQDB_CHAR*)malloc(len * sizeof(SQDB_CHAR)); + vswprintf(m_buf, len, fmt, va); +#endif + + va_end(va); + + return m_buf; +} + +const SQDB_CHAR *QueryStr::Get() const { + return m_buf; +} + +QueryStr::~QueryStr() { +#ifdef SQDB_UTF8 + sqlite3_free(m_buf); +#else + free(m_buf); +#endif +} + +Db::Db(const SQDB_CHAR *fileName) + : RefCount() { +#ifdef SQDB_UTF8 + const int ret = sqlite3_open(fileName, &m_db); +#else + const int ret = sqlite3_open16(fileName, &m_db); +#endif + CHECK(m_db, ret); + + IncRef(); +} + +void Db::BeginTransaction() { + Query(SQDB_MAKE_TEXT("BEGIN;")).Next(); +} + +void Db::CommitTransaction() { + Query(SQDB_MAKE_TEXT("COMMIT;")).Next(); +} + +void Db::RollbackTransaction() { + Query(SQDB_MAKE_TEXT("ROLLBACK;")).Next(); +} + +bool Db::TableExists(const SQDB_CHAR *tableName) { + QueryStr str; + Statement s = + Query( + str.Format(SQDB_MAKE_TEXT("select count(*) from sqlite_master where type='table' and name='%s';"), + tableName)); + s.Next(); + const int count = s.GetField(0); + return count > 0; +} + +Statement Db::Query(const SQDB_CHAR *queryStr) { + sqlite3_stmt *stmt = nullptr; +#ifdef SQDB_UTF8 + const int ret = sqlite3_prepare(m_db, queryStr, -1, &stmt, nullptr); +#else + const int ret = sqlite3_prepare16(m_db, queryStr, -1, &stmt, NULL); +#endif + CHECK(m_db, ret); + + return Statement(m_db, stmt); +} + +long long Db::LastId() { + long long ret = sqlite3_last_insert_rowid(m_db); + return ret; +} + +Db::Db(const Db &x) + : RefCount(x), + m_db(x.m_db) { + IncRef(); +} + +Db &Db::operator=(const Db &x) { + if (this != &x) { + RefCount::operator=(x); + + IncRef(); + m_db = x.m_db; + } + return *this; +} + +Db::~Db() { + if (DecRef() == 0) { + sqlite3_close(m_db); + } +} + -- cgit v1.2.3-54-g00ecf