commit 719e9c17764fdad572142b416461e554b226936c Author: mrbesen Date: Wed Apr 7 11:42:35 2021 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01d9e70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.bin +*.out +build/ +*.exe +.gdb_history + +*.so +*.bmp +*.d + +test +.vscode/settings.json + +tgsearch +tgsearch_strip +log.txt +*.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f4df8f6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdparty/Log"] + path = thirdparty/Log + url = https://git.okaestne.de/okaestne/Log.git +[submodule "thirdparty/libmrbesen"] + path = thirdparty/libmrbesen + url = ssh://gitea@git.mrbesen.de:2222/MrBesen/libmrbesen.git diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..29f82e9 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/src/", + "${workspaceFolder}/thirdparty/Log/", + "${workspaceFolder}/thirdparty/libmrbesen/inc", + "${workspaceFolder}/src/**" + ], + "defines": [], + "compilerPath": "/usr/bin/g++", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "gcc-x64" + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b2ecb70 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,50 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "test Debuggen (gdb)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/test", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "make test", + "miDebuggerPath": "/usr/bin/gdb" + }, + { + "name": "Debuggen (gdb)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/tgsearch", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "make all", + "miDebuggerPath": "/usr/bin/gdb" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..d253c3c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,45 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "make all", + "type": "shell", + "command": "make -j all", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "make clean", + "type": "shell", + "command": "make clean", + "problemMatcher": [] + }, + { + "label": "leak check", + "type": "shell", + "command": "/usr/bin/valgrind --leak-check=full ${workspaceFolder}/tgsearch -s", + "problemMatcher": [], + "dependsOn": "make all" + }, + { + "label": "leak check ALL", + "type": "shell", + "command": "/usr/bin/valgrind --leak-check=full --show-leak-kinds=all ${workspaceFolder}/tgsearch -s", + "problemMatcher": [], + "dependsOn": "make all" + }, + { + "label": "make test", + "type": "shell", + "command": "make -j test", + "problemMatcher": ["$gcc"], + } + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3176bf2 --- /dev/null +++ b/Makefile @@ -0,0 +1,81 @@ +# Author Yannis Gerlach +# Hochschule Osnabrück +# 13.11.2020 + +# `make clean all` nicht mit -j verwenden! -> race condition im make file +# statdessen: `make clean; make all -j` verwenden + +NAME = tgsearch +NAMETEST = test +CFLAGS = -std=c++17 -O2 -g -pipe -Wall -Wextra -Wno-unused-parameter -Wpedantic -rdynamic #-march=native -Wall +CXX = g++ +SRCF = src/ +BUILDDIR = build/ +TESTF = tests/ +DEPF = $(BUILDDIR)deps/ +INCF = ./src/ +INCFS = $(shell find $(INCF) -type d) + +LOGF = ./thirdparty/Log/ +LOGO = $(LOGF)Log.o +LIBMRBESENF = ./thirdparty/libmrbesen/ +LIBMRBESENA = libmrbesen.a +LIBMRBESENALONG = $(LIBMRBESENF)$(LIBMRBESENA) + +INCLUDES = -I$(LOGF) $(addprefix -I, $(INCFS)) -Ithirdparty/libmrbesen/inc +LDFLAGS = + +SRCFILES = $(shell find $(SRCF) -name "*.cpp") +OBJFILES = $(patsubst $(SRCF)%, $(BUILDDIR)%, $(patsubst %.cpp, %.o, $(SRCFILES))) $(LOGO) +DEPFILES = $(wildcard $(DEPF)*.d) + +SOURCEDIRS = $(shell find $(SRCF) -type d -printf "%p/\n") +BUILDDIRS = $(patsubst $(SRCF)%, $(BUILDDIR)%, $(SOURCEDIRS)) + +OBJFILESTEST = $(filter-out $(BUILDDIR)main.o, $(OBJFILES)) + +INCLUDES += $(addprefix -I, $(SOURCEDIRS)) + +all: $(NAME) runtest + +$(NAME): $(BUILDDIRS) $(DEPF) $(OBJFILES) $(LIBMRBESENALONG) + @echo "Linking $@" + @$(CXX) $(CFLAGS) -o $@ $(filter %.o, $^) $(filter %.a, $^) $(LDFLAGS) + +$(BUILDDIR)%.o: $(SRCF)%.cpp + @echo "Compiling: $@" + @$(CXX) $(CFLAGS) $(INCLUDES) $< -MM -MT $@ > $(DEPF)$(subst /,_,$*).d + @$(CXX) -c -o $@ $(CFLAGS) $(INCLUDES) $< + +$(NAME)_strip: $(NAME) + @echo "Strip $<" + @strip -o $@ $< + +%/: + mkdir -p $@ + +clean-depends: + $(RM) -r $(DEPF) + +$(LOGO): + $(MAKE) -C $(LOGF) all + +$(LIBMRBESENALONG): + $(MAKE) -C $(LIBMRBESENF) $(LIBMRBESENA) runtest + +clean: + $(RM) -r $(NAME) $(BUILDDIR) $(NAMETEST) $(NAME)_strip + $(MAKE) -C $(LOGF) $@ + $(MAKE) -C $(LIBMRBESENF) $@ + +$(NAMETEST): $(BUILDDIRS) $(DEPF) $(TESTF)*.cpp $(OBJFILESTEST) $(LIBMRBESENALONG) + @echo "Compiling tests" + @$(CXX) -o $@ $(filter %.o, $^) $(filter %.cpp, $^) $(filter %.a, $^) $(CFLAGS) -I$(SRCF) $(INCLUDES) $(LDFLAGS) + +runtest: $(NAMETEST) + @echo "Running tests" + ./$< + +.PHONY: clean all $(NAMETEST) clean-depends runtest + +include $(DEPFILES) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e950201 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,72 @@ + +#include + +#include +#include //signal handler + +#include "search.h" + +static bool run = true; + +void sig_handler(int sig_num) { + Log::info << "signalHandler triggered"; + run = false; + (void) sig_num; +} + +int main(int argc, const char** argv) { + Log::init(); + Log::setConsoleLogLevel(Log::Level::TRACE); + Log::addLogfile("log.txt", Log::Level::TRACE); +#if __unix__ + Log::setColoredOutput(true); +#endif + + if(argc < 2) { + Log::error << "Usage: " << argv[0] << " [ ....]"; + return 1; + } + + Search search; + for(int i = 1; i < argc; ++i) { + Log::info << "Load File: " << argv[i]; + search.addFile(argv[i]); + } + + signal(SIGINT, sig_handler); + + while(run) { + Log::info << "Enter Search String: "; + std::string searchterm; + std::cin >> searchterm; + + if(!run) break; + + Log::info << "Enter Flags: [RI]"; + std::string flags; + std::cin >> flags; + Searchflags parsedflags = Search::fromString(flags); + + if(!run) break; + + std::list results = search.search(searchterm, parsedflags); + Log::info << results.size() << " results"; + if(results.size()) { + Log::info << "Print results?"; + char c; + std::cin >> c; + + if(!run) break; + + if(c == 'y' || c == 'Y') { + //print results + for(const Message* m : results) { + Log::info << search.getShortChatname(m->chatid) << ": (" << m->messageid << ") " << m->text; + } + } + } + } + + Log::stop(); + return 0; +} diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 0000000..882886c --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,135 @@ +#include "search.h" + +#include +#include +#include +#include + +#include +using json = nlohmann::json; + +Searchflags operator|=(Searchflags& lhs, const Searchflags sf) { + lhs = (Searchflags) ((uint32_t) lhs | (uint32_t) sf); + return lhs; +} + +bool operator&(Searchflags& lhs, const Searchflags sf) { + return (bool) ((uint32_t) lhs & (uint32_t) sf); +} + +Search::Search() {} +Search::~Search() {} + +Searchflags Search::fromString(const std::string& str) { + Searchflags f = Searchflags::NONE; + + for(char c : str) { + switch(c) { + case 'R': + case 'r': + f |= Searchflags::REGEX; + break; + case 'i': + case 'I': + f |= Searchflags::IGNORECASE; + break; + default: + break; + } + } + + return f; +} + +void Search::addFile(const std::string& file) { + //laden den datei + try { + std::ifstream fstream(file); + json j; + fstream >> j; + + if(j.contains("messages")) { + chatnames.insert({j["id"], j["name"].get()}); + loadMessages(j["messages"], j["id"]); + } + } catch (nlohmann::detail::parse_error& e) { + Log::error << "Could not load File: " << e.what(); + } +} + +std::list Search::search(std::string text, Searchflags flags) const { + std::list out; + + if(flags & Searchflags::REGEX) { + searchRegex(text, flags & Searchflags::IGNORECASE, out); + return out; + } + + if(flags & Searchflags::IGNORECASE) { + //turn search to lower + mrbesen::util::toLower(text); + + runsearch(text, &Search::matchesIC, out); + } else { + runsearch(text, &Search::matches, out); + } + + return out; +} + +const std::string& Search::getChatname(uint64_t id) const { + static const std::string UNKOWNCHAT = ""; + auto it = chatnames.find(id); + if(it == chatnames.end()) return UNKOWNCHAT; + return it->second; +} + +std::string Search::getShortChatname(uint64_t id) const { + std::string chatname = getChatname(id); + if(chatname.size() > 14) { + return chatname.substr(0, 14); + } + return chatname; +} + +void Search::searchRegex(const std::string& text, bool ignoreCase, std::list& out) const { + //build regex pattern + std::regex pattern(text, (ignoreCase ? std::regex::icase : (std::regex::flag_type) 0)); +} + +void Search::runsearch(const std::string& st, bool (Search::*checker)(const std::string& msg, const std::string& text) const, std::list& out) const { + for(const Message& m : msgs) { + if((this->*checker)(m.text, st)) { + out.push_back(&m); + } + } +} + +bool Search::matches(const std::string& msg, const std::string& text) const { + //simpler contains check + return (msg.find(text) != std::string::npos); +} + +bool Search::matchesIC(const std::string& msg, const std::string& text) const { + //turn compare string to lower + std::string lower; + mrbesen::util::toLower(msg, lower); + return (lower.find(text) != std::string::npos); +} + +void Search::loadMessages(const json& j, uint64_t chatid) { + uint32_t failed = 0; + for(const json& m : j) { + try { + msgs.push_back({m["text"], chatid, m["id"]}); + } catch(...) { + failed ++; + } + } + + if(failed == 0) { + Log::info << "Messages Loaded"; + } else { + Log::warn << failed << " Messages failed to load"; + } +} \ No newline at end of file diff --git a/src/search.h b/src/search.h new file mode 100644 index 0000000..5846955 --- /dev/null +++ b/src/search.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +#include +using json = nlohmann::json; + +struct Message { + std::string text; + uint64_t chatid; + uint64_t messageid; +}; + +enum class Searchflags { + NONE = 0, + IGNORECASE = 1, + REGEX = 2, +}; + +Searchflags operator|=(Searchflags& lhs, const Searchflags sf); + +bool operator&(Searchflags& lhs, const Searchflags sf); + +class Search { +public: + Search(); + ~Search(); + + static Searchflags fromString(const std::string&); + + void addFile(const std::string& file); + + std::list search(std::string text, Searchflags flags = Searchflags::NONE) const; + const std::string& getChatname(uint64_t id) const; + std::string getShortChatname(uint64_t id) const; +private: + void searchRegex(const std::string& text, bool ignoreCase, std::list& out) const; + + void runsearch(const std::string& st, bool (Search::*checker)(const std::string& msg, const std::string& text) const, std::list& out) const; + + bool matches(const std::string& msg, const std::string& text) const; + bool matchesIC(const std::string& msg, const std::string& text) const; + + void loadMessages(const json& j, uint64_t chatid); + + std::list msgs; + std::map chatnames; +}; \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..f55c453 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,26 @@ +#include +#include "test.h" + +//tests + +test_t tests[] = {NULL}; + +int main(int argc, char** argv) { + + test_t* current = tests; + int failcount = 0; + int testcount = 0; + for(; *current; current++) { + testcount++; + printf("\033[1mRunning test number: %d ", testcount); + if((*current)()) { + printf("\033[1;92msucceeded\033[0;1m!\n"); + } else { + printf("\033[1;91mfailed\033[0;1m\n"); + failcount++; + } + } + + printf("\033[1;93m%d\033[0;1m/%d failed\n", failcount, testcount); + return failcount > 0; +} diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..890d25b --- /dev/null +++ b/tests/test.h @@ -0,0 +1,11 @@ +#define TESTFAILED 0 +#define TESTGOOD 1 + +#include + +#define TESTDATA "./tests/data/" + +#define ASSERT(BED, ERR) if(!(BED)) { std::cout << __FILE__ << ":" << __LINE__ << " " << ERR << std::endl; return TESTFAILED; } +// #define ASSERT(BED) ASSERT(BED, "") + +typedef int (*test_t)(); diff --git a/thirdparty/Log b/thirdparty/Log new file mode 160000 index 0000000..4e4e85f --- /dev/null +++ b/thirdparty/Log @@ -0,0 +1 @@ +Subproject commit 4e4e85fe35ffcf557a9972390139e272f0fb6dc0 diff --git a/thirdparty/libmrbesen b/thirdparty/libmrbesen new file mode 160000 index 0000000..e6dcb27 --- /dev/null +++ b/thirdparty/libmrbesen @@ -0,0 +1 @@ +Subproject commit e6dcb27ecb0dbe2258b7ee83a44a01da9da97216