M .gitlab-ci.yml => .gitlab-ci.yml +11 -14
@@ 22,7 22,7 @@ variables:
UDNS: "-DWITH_UDNS=1"
SYSTEMD: "-DWITH_SYSTEMD=1"
LIBIDN: "-DWITH_LIBIDN=1"
- LITESQL: "-DWITH_LITESQL=1"
+ SQLITE3: "-DWITH_SQLITE3=1"
#
## Build jobs
@@ 33,8 33,8 @@ variables:
tags:
- docker
script:
- - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}"
- - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
+ - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}"
+ - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}
- make everything -j$(nproc || echo 1)
- make coverage_check -j$(nproc || echo 1)
artifacts:
@@ 77,19 77,19 @@ build:2:
build:3:
variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
+ SQLITE3: "-DWITHOUT_SQLITE3=1"
<<: *fedora_build
build:4:
variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
+ SQLITE3: "-DWITHOUT_SQLITE3=1"
BOTAN: "-DWITHOUT_BOTAN=1"
LIBIDN: "-DWITHOUT_LIBIDN=1"
<<: *fedora_build
build:5:
variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
+ SQLITE3: "-DWITHOUT_SQLITE3=1"
UDNS: "-DWITHOUT_UDNS=1"
<<: *fedora_build
@@ 156,14 156,14 @@ test:alpine:
test:freebsd:
only:
- - master@louiz/biboumi
+ - branches@louiz/biboumi
tags:
- freebsd
variables:
SYSTEMD: "-DWITHOUT_SYSTEMD=1"
stage: test
script:
- - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
+ - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${SQLITE3}
- make check
- make e2e
@@ 240,7 240,7 @@ codecov:build:7:
coverity:
stage: external
only:
- - master@louiz/biboumi
+ - branches@louiz/biboumi
tags:
- docker
image: docker.louiz.org/biboumi-test-fedora:latest
@@ 298,16 298,13 @@ packaging:deb:
packaging:archlinux:
stage: packaging
+ only:
+ - master@louiz/biboumi
tags:
- docker
image: docker.louiz.org/biboumi-test-archlinux:latest
before_script: []
script:
- - sudo pacman -Syuu --noconfirm
- - git clone https://aur.archlinux.org/litesql-git.git
- - cd litesql-git
- - makepkg -si --noconfirm
- - cd ..
- git clone https://aur.archlinux.org/biboumi-git.git
- cd biboumi-git
- makepkg -si --noconfirm
M CHANGELOG.rst => CHANGELOG.rst +6 -0
@@ 1,3 1,9 @@
+Version 6.0
+===========
+
+ - The LiteSQL dependency was removed. Only libsqlite3 is now necessary
+ to work with the database.
+
Version 5.0 - 2017-05-24
========================
M CMakeLists.txt => CMakeLists.txt +11 -20
@@ 124,10 124,10 @@ elseif(NOT WITHOUT_UDNS)
find_package(UDNS)
endif()
-if(WITH_LITESQL)
- find_package(LITESQL REQUIRED)
-elseif(NOT WITHOUT_LITESQL)
- find_package(LITESQL)
+if(WITH_SQLITE3)
+ find_package(SQLITE3 REQUIRED)
+elseif(NOT WITHOUT_SQLITE3)
+ find_package(SQLITE3)
endif()
#
@@ 158,17 158,14 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}/")
file(GLOB source_utils
src/utils/*.[hc]pp)
add_library(utils OBJECT ${source_utils})
-add_dependencies(utils litesql_generated_sources)
file(GLOB source_irc
src/irc/*.[hc]pp)
add_library(irc OBJECT ${source_irc})
-add_dependencies(irc litesql_generated_sources)
file(GLOB source_xmpp
src/xmpp/*.[hc]pp)
add_library(xmpp OBJECT ${source_xmpp})
-add_dependencies(xmpp litesql_generated_sources)
file(GLOB source_identd
src/identd/*.[hc]pp)
@@ 177,7 174,6 @@ add_library(identd OBJECT ${source_identd})
file(GLOB source_bridge
src/bridge/*.[hc]pp)
add_library(bridge OBJECT ${source_bridge})
-add_dependencies(bridge litesql_generated_sources)
file(GLOB source_config
src/config/*.[hc]pp)
@@ 191,20 187,15 @@ file(GLOB source_network
src/network/*.[hc]pp)
add_library(network OBJECT ${source_network})
-if(LITESQL_FOUND)
- LITESQL_GENERATE_CPP("database/database.xml"
- "biboudb"
- LITESQL_GENERATED_SOURCES)
- add_custom_target(litesql_generated_sources SOURCES ${LITESQL_GENERATED_SOURCES})
+if(SQLITE3_FOUND)
+ file(GLOB source_database
+ src/database/*.[hc]pp)
+ add_library(database OBJECT ${source_database})
- add_library(database OBJECT src/database/database.cpp ${LITESQL_GENERATED_SOURCES})
- add_dependencies(database litesql_generated_sources)
-
- include_directories(database ${LITESQL_INCLUDE_DIRS})
+ include_directories(database ${SQLITE3_INCLUDE_DIRS})
set(USE_DATABASE TRUE)
else()
add_library(database OBJECT "")
- add_custom_target(litesql_generated_sources)
endif()
#
@@ 269,8 260,8 @@ if(LIBIDN_FOUND)
target_link_libraries(test_suite ${LIBIDN_LIBRARIES})
endif()
if(USE_DATABASE)
- target_link_libraries(${PROJECT_NAME} ${LITESQL_LIBRARIES})
- target_link_libraries(test_suite ${LITESQL_LIBRARIES})
+ target_link_libraries(${PROJECT_NAME} ${SQLITE3_LIBRARIES})
+ target_link_libraries(test_suite ${SQLITE3_LIBRARIES})
endif()
# Define a __FILENAME__ macro with the relative path (from the base project directory)
M CONTRIBUTING.rst => CONTRIBUTING.rst +3 -3
@@ 52,8 52,8 @@ There are two test suites for biboumi:
uses a specific IRC server (`charybdis`_), and only tests the most complete
biboumi configuration (when all dependencies are used). To run it, you need
to install various dependencies: refer to fedora’s `Dockerfile.base`_ and
- `Dockerfile`_ to see how to install charybdis, slixmpp, botan, litesql, an
- ssl certificate, etc.
+ `Dockerfile`_ to see how to install charybdis, slixmpp, botan, a ssl
+ certificate, etc.
Once all the dependencies are correctly installed, the tests are run with
@@ 94,4 94,4 @@ Please try to follow the existing style:
.. _Dockerfile.base: docker/biboumi-test/fedora/Dockerfile.base
.. _Dockerfile: docker/biboumi-test/fedora/Dockerfile
.. _charybdis: https://github.com/charybdis-ircd/charybdis
-.. _the __main__.py file: tests/end_to_end/__main__.py>
\ No newline at end of file
+.. _the __main__.py file: tests/end_to_end/__main__.py
M INSTALL.rst => INSTALL.rst +7 -6
@@ 32,6 32,12 @@ libiconv_
libuuid_
Generate unique IDs
+sqlite3_ (option, but highly recommended)
+ Provides a way to store various options in a (sqlite3) database. Each user
+ of the gateway can store their own values (for example their prefered port,
+ or their IRC password). Without this dependency, many interesting features
+ are missing.
+
libidn_ (optional, but recommended)
Provides the stringprep functionality. Without it, JIDs for IRC users are
not provided.
@@ 48,11 54,6 @@ libbotan_ 1.11 or 2.0 (optional)
gcrypt_ (mandatory only if botan is absent)
Provides the SHA-1 hash function, for the case where Botan is absent.
-litesql_ (optional)
- Provides a way to store various options in a (sqlite3) database. Each user
- of the gateway can store their own values (for example their prefered port,
- or their IRC password).
-
systemd_ (optional)
Provides the support for a systemd service of Type=notify. This is useful only
if you are packaging biboumi in a distribution with Systemd.
@@ 160,7 161,7 @@ to use biboumi.
.. _libidn: http://www.gnu.org/software/libidn/
.. _libbotan: http://botan.randombit.net/
.. _udns: http://www.corpit.ru/mjt/udns.html
-.. _litesql: http://git.louiz.org/litesql
+.. _sqlite3: https://sqlite.org
.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/
.. _biboumi.1.rst: doc/biboumi.1.rst
.. _gcrypt: https://www.gnu.org/software/libgcrypt/
D cmake/Modules/FindLITESQL.cmake => cmake/Modules/FindLITESQL.cmake +0 -76
@@ 1,76 0,0 @@
-# - Find LiteSQL
-#
-# Find the LiteSQL library, and defines a function to generate C++ files
-# from the database xml file using litesql-gen fro
-#
-# This module defines the following variables:
-# LITESQL_FOUND - True if library and include directory are found
-# If set to TRUE, the following are also defined:
-# LITESQL_INCLUDE_DIRS - The directory where to find the header file
-# LITESQL_LIBRARIES - Where to find the library file
-# LITESQL_GENERATE_CPP - A function, to be used like this:
-# LITESQL_GENERATE_CPP("db/database.xml" # The file defining the db schemas
-# "database" # The name of the C++ “module”
-# # that will be generated
-# LITESQL_GENERATED_SOURCES # Variable containing the
-# resulting C++ files to compile
-#
-# For conveniance, these variables are also set. They have the same values
-# than the variables above. The user can thus choose his/her prefered way
-# to write them.
-# LITESQL_INCLUDE_DIR
-# LITESQL_LIBRARY
-#
-# This file is in the public domain
-
-find_path(LITESQL_INCLUDE_DIRS NAMES litesql.hpp
- DOC "The LiteSQL include directory")
-
-find_library(LITESQL_LIBRARIES NAMES litesql
- DOC "The LiteSQL library")
-
-foreach(DB_TYPE sqlite postgresql mysql ocilib)
- string(TOUPPER ${DB_TYPE} DB_TYPE_UPPER)
- find_library(LITESQL_${DB_TYPE_UPPER}_LIB_PATH NAMES litesql_${DB_TYPE}
- DOC "The ${DB_TYPE} backend for LiteSQL")
- if(LITESQL_${DB_TYPE_UPPER}_LIB_PATH)
- list(APPEND LITESQL_LIBRARIES ${LITESQL_${DB_TYPE_UPPER}_LIB_PATH})
- endif()
- mark_as_advanced(LITESQL_${DB_TYPE_UPPER}_LIB_PATH)
-endforeach()
-
-find_program(LITESQLGEN_EXECUTABLE NAMES litesql-gen
- DOC "The utility that creates .h and .cpp files from a xml database description")
-
-# Use some standard module to handle the QUIETLY and REQUIRED arguments, and
-# set LITESQL_FOUND to TRUE if these two variables are set.
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(LITESQL REQUIRED_VARS LITESQL_LIBRARIES LITESQL_INCLUDE_DIRS
- LITESQLGEN_EXECUTABLE)
-
-# Compatibility for all the ways of writing these variables
-if(LITESQL_FOUND)
- set(LITESQL_INCLUDE_DIR ${LITESQL_INCLUDE_DIRS})
- set(LITESQL_LIBRARY ${LITESQL_LIBRARIES})
-endif()
-
-mark_as_advanced(LITESQL_INCLUDE_DIRS LITESQL_LIBRARIES LITESQLGEN_EXECUTABLE)
-
-
-# LITESQL_GENERATE_CPP function
-
-function(LITESQL_GENERATE_CPP
- SOURCE_FILE OUTPUT_NAME OUTPUT_SOURCES)
- set(${OUTPUT_SOURCES})
- add_custom_command(
- OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.cpp"
- "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.hpp"
- COMMAND ${LITESQLGEN_EXECUTABLE}
- ARGS -t c++ --output-dir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE}
- DEPENDS ${SOURCE_FILE}
- COMMENT "Running litesql-gen on ${SOURCE_FILE}"
- VERBATIM)
- list(APPEND ${OUTPUT_SOURCES} "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.cpp")
- set_source_files_properties(${${OUTPUT_SOURCES}} PROPERTIES GENERATED TRUE)
- set(${OUTPUT_SOURCES} ${${OUTPUT_SOURCES}} PARENT_SCOPE)
-endfunction()
A cmake/Modules/FindSQLITE3.cmake => cmake/Modules/FindSQLITE3.cmake +43 -0
@@ 0,0 1,43 @@
+# - Find sqlite3
+# Find the sqlite3 cryptographic library
+#
+# This module defines the following variables:
+# SQLITE3_FOUND - True if library and include directory are found
+# If set to TRUE, the following are also defined:
+# SQLITE3_INCLUDE_DIRS - The directory where to find the header file
+# SQLITE3_LIBRARIES - Where to find the library file
+#
+# For conveniance, these variables are also set. They have the same values
+# than the variables above. The user can thus choose his/her prefered way
+# to write them.
+# SQLITE3_LIBRARY
+# SQLITE3_INCLUDE_DIR
+#
+# This file is in the public domain
+
+include(FindPkgConfig)
+
+if(NOT SQLITE3_FOUND)
+ pkg_check_modules(SQLITE3 sqlite3)
+endif()
+
+if(NOT SQLITE3_FOUND)
+ find_path(SQLITE3_INCLUDE_DIRS NAMES sqlite3.h
+ DOC "The sqlite3 include directory")
+
+ find_library(SQLITE3_LIBRARIES NAMES sqlite3
+ DOC "The sqlite3 library")
+
+ # Use some standard module to handle the QUIETLY and REQUIRED arguments, and
+ # set SQLITE3_FOUND to TRUE if these two variables are set.
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(SQLITE3 REQUIRED_VARS SQLITE3_LIBRARIES SQLITE3_INCLUDE_DIRS)
+
+ if(SQLITE3_FOUND)
+ set(SQLITE3_LIBRARY ${SQLITE3_LIBRARIES} CACHE INTERNAL "")
+ set(SQLITE3_INCLUDE_DIR ${SQLITE3_INCLUDE_DIRS} CACHE INTERNAL "")
+ set(SQLITE3_FOUND ${SQLITE3_FOUND} CACHE INTERNAL "")
+ endif()
+endif()
+
+mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES)<
\ No newline at end of file
D database/database.xml => database/database.xml +0 -70
@@ 1,70 0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE database SYSTEM "litesql.dtd">
-
-<database name="BibouDB" namespace="db">
- <object name="GlobalOptions">
- <field name="owner" type="string" length="3071"/>
-
- <field name="maxHistoryLength" type="integer" default="20"/>
- <field name="recordHistory" type="boolean" default="true"/>
- <index unique="true">
- <indexfield name="owner"/>
- </index>
- </object>
-
- <object name="IrcServerOptions">
- <field name="owner" type="string" length="3071"/>
- <field name="server" type="string" length="3071"/>
-
- <field name="pass" type="string" length="1024" default=""/>
- <field name="afterConnectionCommand" type="string" length="510" default=""/>
- <field name="tlsPorts" type="string" length="4096" default="6697;6670" />
- <field name="ports" type="string" length="4096" default="6667" />
- <field name="username" type="string" length="1024" default=""/>
- <field name="realname" type="string" length="1024" default=""/>
- <field name="verifyCert" type="boolean" default="true"/>
- <field name="trustedFingerprint" type="string"/>
-
- <field name="encodingOut" type="string" default="ISO-8859-1"/>
- <field name="encodingIn" type="string" default="ISO-8859-1"/>
-
- <field name="maxHistoryLength" type="integer" default="20"/>
- <index unique="true">
- <indexfield name="owner"/>
- <indexfield name="server"/>
- </index>
- </object>
-
- <object name="IrcChannelOptions">
- <field name="owner" type="string" length="3071"/>
- <field name="server" type="string" length="3071"/>
- <field name="channel" type="string" length="1024"/>
-
- <field name="encodingOut" type="string"/>
- <field name="encodingIn" type="string"/>
-
- <field name="maxHistoryLength" type="integer" default="20"/>
-
- <field name="persistent" type="boolean" default="false"/>
-
- <index unique="true">
- <indexfield name="owner"/>
- <indexfield name="server"/>
- <indexfield name="channel"/>
- </index>
- </object>
-
- <object name="MucLogLine">
- <field name="uuid" type="string" length="36" />
- <!-- The bare JID of the user for which we stored the line. It's
- the JID associated with the Bridge -->
- <field name="owner" type="string" length="4096" />
- <!-- The room IID -->
- <field name="ircChanName" type="string" length="4096" />
- <field name="ircServerName" type="string" length="4096" />
-
- <field name="date" type="datetime" />
- <field name="body" type="string" length="65536"/>
- <field name="nick" type="string" length="4096" />
- </object>
-</database>
M docker/biboumi-test/alpine/Dockerfile => docker/biboumi-test/alpine/Dockerfile +47 -3
@@ 1,10 1,54 @@
# This Dockerfile creates a docker image suitable to run biboumi’s build and
# tests. For example, it can be used on with gitlab-ci.
-FROM docker.louiz.org/biboumi-test-alpine-base
+FROM docker.io/alpine:latest
-# Install litesql
-RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && rm -rf /litesql && ldconfig || true
+ENV LC_ALL C.UTF-8
+
+# Needed to build biboumi
+RUN apk add --no-cache g++\
+ clang\
+ valgrind\
+ udns-dev\
+ c-ares-dev\
+ sqlite-dev\
+ libuuid\
+ util-linux-dev\
+ libgcrypt-dev\
+ cmake\
+ make\
+ expat-dev\
+ libidn-dev\
+ git\
+ py3-lxml\
+ libtool\
+ py3-pip\
+ python2\
+ python3-dev\
+ automake\
+ autoconf\
+ flex\
+ bison\
+ libltdl\
+ openssl\
+ libressl-dev\
+ zlib-dev\
+ curl
+
+# Install botan
+RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
+
+# Install slixmpp, for e2e tests
+RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
+RUN adduser tester -D -h /home/tester
+
+# Install charybdis, for e2e tests
+RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install && rm -rf /charybdis
+
+RUN chown -R tester:tester /home/tester/ircd
+
+RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
WORKDIR /home/tester
USER tester
D docker/biboumi-test/alpine/Dockerfile.base => docker/biboumi-test/alpine/Dockerfile.base +0 -51
@@ 1,51 0,0 @@
-# This Dockerfile creates a docker image suitable to run biboumi’s build and
-# tests. For example, it can be used on with gitlab-ci.
-
-FROM docker.io/alpine:latest
-
-ENV LC_ALL C.UTF-8
-
-# Needed to build biboumi
-RUN apk add --no-cache g++\
- clang\
- valgrind\
- udns-dev\
- c-ares-dev\
- sqlite-dev\
- libuuid\
- util-linux-dev\
- libgcrypt-dev\
- cmake\
- make\
- expat-dev\
- libidn-dev\
- git\
- py3-lxml\
- libtool\
- py3-pip\
- python2\
- python3-dev\
- automake\
- autoconf\
- flex\
- bison\
- libltdl\
- openssl\
- libressl-dev\
- zlib-dev\
- curl
-
-# Install botan
-RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
-
-# Install slixmpp, for e2e tests
-RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
-
-RUN adduser tester -D -h /home/tester
-
-# Install charybdis, for e2e tests
-RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install && rm -rf /charybdis
-
-RUN chown -R tester:tester /home/tester/ircd
-
-RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
M docker/biboumi-test/debian/Dockerfile => docker/biboumi-test/debian/Dockerfile +53 -3
@@ 1,10 1,60 @@
# This Dockerfile creates a docker image suitable to run biboumi’s build and
# tests. For example, it can be used on with gitlab-ci.
-FROM docker.louiz.org/biboumi-test-debian-base
+FROM docker.io/debian:latest
-# Install litesql
-RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && rm -rf /litesql && ldconfig
+ENV LC_ALL C.UTF-8
+
+RUN apt update
+
+# Needed to build biboumi
+RUN apt install -y g++\
+ clang\
+ valgrind\
+ libudns-dev\
+ libc-ares-dev\
+ libsqlite3-dev\
+ libuuid1\
+ libgcrypt20-dev\
+ cmake\
+ make\
+ libexpat1-dev\
+ libidn11-dev\
+ uuid-dev\
+ libsystemd-dev\
+ pandoc\
+ libasan1\
+ libubsan0\
+ git\
+ python3-lxml\
+ lcov\
+ libtool\
+ python3-pip\
+ python3-dev\
+ automake\
+ autoconf\
+ flex\
+ bison\
+ libltdl-dev\
+ openssl\
+ zlib1g-dev\
+ libssl-dev\
+ curl
+
+# Install botan
+RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
+
+# Install slixmpp, for e2e tests
+RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
+RUN useradd tester -m
+
+# Install charybdis, for e2e tests
+RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install && rm -rf /charybdis
+
+RUN chown -R tester:tester /home/tester/ircd
+
+RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
WORKDIR /home/tester
USER tester
D docker/biboumi-test/debian/Dockerfile.base => docker/biboumi-test/debian/Dockerfile.base +0 -57
@@ 1,57 0,0 @@
-# This Dockerfile creates a docker image suitable to run biboumi’s build and
-# tests. For example, it can be used on with gitlab-ci.
-
-FROM docker.io/debian:latest
-
-ENV LC_ALL C.UTF-8
-
-RUN apt update
-
-# Needed to build biboumi
-RUN apt install -y g++\
- clang\
- valgrind\
- libudns-dev\
- libc-ares-dev\
- libsqlite3-dev\
- libuuid1\
- libgcrypt20-dev\
- cmake\
- make\
- libexpat1-dev\
- libidn11-dev\
- uuid-dev\
- libsystemd-dev\
- pandoc\
- libasan1\
- libubsan0\
- git\
- python3-lxml\
- lcov\
- libtool\
- python3-pip\
- python3-dev\
- automake\
- autoconf\
- flex\
- bison\
- libltdl-dev\
- openssl\
- zlib1g-dev\
- libssl-dev\
- curl
-
-# Install botan
-RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
-
-# Install slixmpp, for e2e tests
-RUN git clone https://github.com/saghul/aiodns.git && cd aiodns && git checkout 7ee13f9bea25784322~ && python3 setup.py build && python3 setup.py install && git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
-
-RUN useradd tester -m
-
-# Install charybdis, for e2e tests
-RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install && rm -rf /charybdis
-
-RUN chown -R tester:tester /home/tester/ircd
-
-RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
M docker/biboumi-test/fedora/Dockerfile => docker/biboumi-test/fedora/Dockerfile +55 -3
@@ 1,10 1,62 @@
# This Dockerfile creates a docker image suitable to run biboumi’s build and
# tests. For example, it can be used on with gitlab-ci.
-FROM docker.louiz.org/biboumi-test-fedora-base
+FROM docker.io/fedora:latest
-# Install litesql
-RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && ldconfig && rm -rf /litesql
+ENV LC_ALL C.UTF-8
+
+RUN dnf --refresh install -y\
+ gcc-c++\
+ clang\
+ valgrind\
+ udns-devel\
+ c-ares-devel\
+ sqlite-devel\
+ libuuid-devel\
+ libgcrypt-devel\
+ cmake\
+ make\
+ expat-devel\
+ libidn-devel\
+ uuid-devel\
+ systemd-devel\
+ pandoc\
+ libasan\
+ libubsan\
+ git\
+ fedora-packager\
+ python3-lxml\
+ lcov\
+ rpmdevtools\
+ python3-devel\
+ automake\
+ autoconf\
+ flex\
+ flex-devel\
+ bison\
+ libtool-ltdl-devel\
+ libtool\
+ openssl-devel\
+ which\
+ java-1.8.0-openjdk\
+ && dnf clean all
+
+# Install botan
+RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && ldconfig && rm -rf /botan
+
+# Install slixmpp, for e2e tests
+RUN git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
+
+RUN useradd tester
+
+# Install charybdis, for e2e tests
+RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin --with-included-boost && make -j8 && make install && rm -rf /charybdis
+
+RUN chown -R tester:tester /home/tester/ircd
+
+RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
+
+COPY coverity /home/tester/coverity
WORKDIR /home/tester
USER tester
D docker/biboumi-test/fedora/Dockerfile.base => docker/biboumi-test/fedora/Dockerfile.base +0 -59
@@ 1,59 0,0 @@
-# This Dockerfile creates a docker image suitable to run biboumi’s build and
-# tests. For example, it can be used on with gitlab-ci.
-
-FROM docker.io/fedora:latest
-
-ENV LC_ALL C.UTF-8
-
-RUN dnf --refresh install -y\
- gcc-c++\
- clang\
- valgrind\
- udns-devel\
- c-ares-devel\
- sqlite-devel\
- libuuid-devel\
- libgcrypt-devel\
- cmake\
- make\
- expat-devel\
- libidn-devel\
- uuid-devel\
- systemd-devel\
- pandoc\
- libasan\
- libubsan\
- git\
- fedora-packager\
- python3-lxml\
- lcov\
- rpmdevtools\
- python3-devel\
- automake\
- autoconf\
- flex\
- flex-devel\
- bison\
- libtool-ltdl-devel\
- libtool\
- openssl-devel\
- which\
- java-1.8.0-openjdk\
- && dnf clean all
-
-# Install botan
-RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && ldconfig && rm -rf /botan
-
-# Install slixmpp, for e2e tests
-RUN git clone git://git.louiz.org/slixmpp && pip3 install pyasn1 && cd slixmpp && python3 setup.py build && python3 setup.py install
-
-RUN useradd tester
-
-# Install charybdis, for e2e tests
-RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis && cd /charybdis && git checkout 4f2b9a4 && sed s/113/1113/ -i /charybdis/authd/providers/ident.c && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin --with-included-boost && make -j8 && make install && rm -rf /charybdis
-
-RUN chown -R tester:tester /home/tester/ircd
-
-RUN yes "" | openssl req -nodes -x509 -newkey rsa:4096 -keyout /home/tester/ircd/etc/ssl.key -out /home/tester/ircd/etc/ssl.pem
-
-COPY coverity /home/tester/coverity
M docker/biboumi/Dockerfile => docker/biboumi/Dockerfile +1 -4
@@ 18,15 18,12 @@ RUN apk add --no-cache\
# Install botan
RUN git clone https://github.com/randombit/botan.git && cd botan && ./configure.py --prefix=/usr && make -j8 && make install && rm -rf /botan
-# Install litesql
-RUN git clone git://git.louiz.org/litesql && mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 && cd /litesql/build && make install && rm -rf /litesql
-
# Install biboumi
RUN git clone git://git.louiz.org/biboumi && mkdir ./biboumi/build && cd ./biboumi/build &&\
cmake .. -DCMAKE_INSTALL_PREFIX=/usr\
-DCMAKE_BUILD_TYPE=Release\
-DWITH_BOTAN=1\
- -DWITH_LITESQL=1\
+ -DWITH_SQLITE3=1\
-DWITH_LIBIDN=1\
&& make -j8 && make install && rm -rf /biboumi
M src/bridge/bridge.cpp => src/bridge/bridge.cpp +12 -10
@@ 23,22 23,24 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
#ifdef USE_DATABASE
const auto jid = bridge.get_bare_jid();
auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
- return options.encodingIn.value();
+ auto result = options.col<Database::EncodingIn>();
+ if (!result.empty())
+ return result;
#else
(void)bridge;
(void)iid;
- return {"ISO-8859-1"};
#endif
+ return {"ISO-8859-1"};
}
Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
- user_jid(std::move(user_jid)),
+ user_jid(std::move(user_jid)),
xmpp(xmpp),
poller(poller)
{
#ifdef USE_DATABASE
const auto options = Database::get_global_options(this->user_jid);
- this->set_record_history(options.recordHistory.value());
+ this->set_record_history(options.col<Database::RecordHistory>());
#endif
}
@@ 258,7 260,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(line);
if (this->record_history)
- uuid = Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
+ uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), irc->get_own_nick());
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
@@ 436,7 438,7 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con
#ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
iid.get_server(), iid.get_local());
- persistent = coptions.persistent.value();
+ persistent = coptions.col<Database::Persistent>();
#endif
if (channel->joined && !channel->parting && !persistent)
{
@@ 837,7 839,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(body, encoding);
if (!nick.empty() && this->record_history)
- Database::store_muc_message(this->get_bare_jid(), iid, std::chrono::system_clock::now(),
+ Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
std::get<0>(xmpp_body), nick);
#endif
for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
@@ 994,12 996,12 @@ void Bridge::send_room_history(const std::string& hostname, std::string chan_nam
{
#ifdef USE_DATABASE
const auto coptions = Database::get_irc_channel_options_with_server_and_global_default(this->user_jid, hostname, chan_name);
- const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.maxHistoryLength.value());
+ const auto lines = Database::get_muc_logs(this->user_jid, chan_name, hostname, coptions.col<Database::MaxHistoryLength>());
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines)
{
- const auto seconds = line.date.value().timeStamp();
- this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(),
+ const auto seconds = line.col<Database::Date>();
+ this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(),
this->user_jid + "/" + resource, seconds);
}
#else
A src/database/column.hpp => src/database/column.hpp +17 -0
@@ 0,0 1,17 @@
+#pragma once
+
+#include <cstdint>
+
+template <typename T>
+struct Column
+{
+ Column(T default_value):
+ value{default_value} {}
+ Column():
+ value{} {}
+ using real_type = T;
+ T value{};
+};
+
+struct Id: Column<std::size_t> { static constexpr auto name = "id_";
+ static constexpr auto options = "PRIMARY KEY AUTOINCREMENT"; };
A src/database/count_query.hpp => src/database/count_query.hpp +35 -0
@@ 0,0 1,35 @@
+#pragma once
+
+#include <database/query.hpp>
+#include <database/table.hpp>
+
+#include <string>
+
+#include <sqlite3.h>
+
+struct CountQuery: public Query
+{
+ CountQuery(std::string name):
+ Query("SELECT count(*) FROM ")
+ {
+ this->body += std::move(name);
+ }
+
+ int64_t execute(sqlite3* db)
+ {
+ auto statement = this->prepare(db);
+ int64_t res = 0;
+ if (sqlite3_step(statement.get()) == SQLITE_ROW)
+ res = sqlite3_column_int64(statement.get(), 0);
+ else
+ {
+ log_error("Count request didn’t return a result");
+ return 0;
+ }
+ if (sqlite3_step(statement.get()) != SQLITE_DONE)
+ log_warning("Count request returned more than one result.");
+
+ log_debug("Returning count: ", res);
+ return res;
+ }
+};
M src/database/database.cpp => src/database/database.cpp +97 -107
@@ 2,175 2,166 @@
#ifdef USE_DATABASE
#include <database/database.hpp>
-#include <logger/logger.hpp>
-#include <irc/iid.hpp>
#include <uuid/uuid.h>
#include <utils/get_first_non_empty.hpp>
#include <utils/time.hpp>
-using namespace std::string_literals;
+#include <sqlite3.h>
-std::unique_ptr<db::BibouDB> Database::db;
+sqlite3* Database::db;
+Database::MucLogLineTable Database::muc_log_lines("MucLogLine_");
+Database::GlobalOptionsTable Database::global_options("GlobalOptions_");
+Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_");
+Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_");
-void Database::open(const std::string& filename, const std::string& db_type)
+void Database::open(const std::string& filename)
{
- try
- {
- auto new_db = std::make_unique<db::BibouDB>(db_type,
- "database="s + filename);
- if (new_db->needsUpgrade())
- new_db->upgrade();
- Database::db = std::move(new_db);
- } catch (const litesql::DatabaseError& e) {
- log_error("Failed to open database ", filename, ". ", e.what());
- throw;
- }
+ auto res = sqlite3_open_v2(filename.data(), &Database::db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
+ log_debug("open: ", res);
+ Database::muc_log_lines.create(Database::db);
+ Database::global_options.create(Database::db);
+ Database::irc_server_options.create(Database::db);
+ Database::irc_channel_options.create(Database::db);
}
-void Database::set_verbose(const bool val)
-{
- Database::db->verbose = val;
-}
-db::GlobalOptions Database::get_global_options(const std::string& owner)
+Database::GlobalOptions Database::get_global_options(const std::string& owner)
{
- try {
- auto options = litesql::select<db::GlobalOptions>(*Database::db,
- db::GlobalOptions::Owner == owner).one();
- return options;
- } catch (const litesql::NotFound& e) {
- db::GlobalOptions options(*Database::db);
- options.owner = owner;
- return options;
- }
+ auto request = Database::global_options.select().where() << Owner{} << "=" << owner;
+
+ Database::GlobalOptions options{Database::global_options.get_name()};
+ auto result = request.execute(Database::db);
+ if (result.size() == 1)
+ options = result.front();
+ else
+ options.col<Owner>() = owner;
+ return options;
}
-db::IrcServerOptions Database::get_irc_server_options(const std::string& owner,
- const std::string& server)
+Database::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server)
{
- try {
- auto options = litesql::select<db::IrcServerOptions>(*Database::db,
- db::IrcServerOptions::Owner == owner &&
- db::IrcServerOptions::Server == server).one();
- return options;
- } catch (const litesql::NotFound& e) {
- db::IrcServerOptions options(*Database::db);
- options.owner = owner;
- options.server = server;
- // options.update();
- return options;
- }
+ auto request = Database::irc_server_options.select().where() << Owner{} << "=" << owner << " and " << Server{} << "=" << server;
+
+ Database::IrcServerOptions options{Database::irc_server_options.get_name()};
+ auto result = request.execute(Database::db);
+ if (result.size() == 1)
+ options = result.front();
+ else
+ {
+ options.col<Owner>() = owner;
+ options.col<Server>() = server;
+ }
+ return options;
}
-db::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner,
- const std::string& server,
- const std::string& channel)
+Database::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, const std::string& server, const std::string& channel)
{
- try {
- auto options = litesql::select<db::IrcChannelOptions>(*Database::db,
- db::IrcChannelOptions::Owner == owner &&
- db::IrcChannelOptions::Server == server &&
- db::IrcChannelOptions::Channel == channel).one();
- return options;
- } catch (const litesql::NotFound& e) {
- db::IrcChannelOptions options(*Database::db);
- options.owner = owner;
- options.server = server;
- options.channel = channel;
- return options;
- }
+ auto request = Database::irc_channel_options.select().where() << Owner{} << "=" << owner <<\
+ " and " << Server{} << "=" << server <<\
+ " and " << Channel{} << "=" << channel;
+ Database::IrcChannelOptions options{Database::irc_channel_options.get_name()};
+ auto result = request.execute(Database::db);
+ if (result.size() == 1)
+ options = result.front();
+ else
+ {
+ options.col<Owner>() = owner;
+ options.col<Server>() = server;
+ options.col<Channel>() = channel;
+ }
+ return options;
}
-db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner,
- const std::string& server,
- const std::string& channel)
+Database::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, const std::string& server,
+ const std::string& channel)
{
auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server);
- coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
- soptions.encodingIn.value());
- coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
- soptions.encodingOut.value());
+ coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
+ soptions.col<EncodingIn>());
+ coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
+ soptions.col<EncodingOut>());
- coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
- soptions.maxHistoryLength.value());
+ coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
+ soptions.col<MaxHistoryLength>());
return coptions;
}
-db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner,
- const std::string& server,
- const std::string& channel)
+Database::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_default(const std::string& owner, const std::string& server, const std::string& channel)
{
auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server);
auto goptions = Database::get_global_options(owner);
- coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
- soptions.encodingIn.value());
- coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
- soptions.encodingOut.value());
+ coptions.col<EncodingIn>() = get_first_non_empty(coptions.col<EncodingIn>(),
+ soptions.col<EncodingIn>());
- coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
- soptions.maxHistoryLength.value(),
- goptions.maxHistoryLength.value());
+ coptions.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
+ soptions.col<EncodingOut>());
+
+ coptions.col<MaxHistoryLength>() = get_first_non_empty(coptions.col<MaxHistoryLength>(),
+ soptions.col<MaxHistoryLength>(),
+ goptions.col<MaxHistoryLength>());
return coptions;
}
-std::string Database::store_muc_message(const std::string& owner, const Iid& iid,
- Database::time_point date,
- const std::string& body,
- const std::string& nick)
+std::string Database::store_muc_message(const std::string& owner, const std::string& chan_name,
+ const std::string& server_name, Database::time_point date,
+ const std::string& body, const std::string& nick)
{
- db::MucLogLine line(*Database::db);
+ auto line = Database::muc_log_lines.row();
auto uuid = Database::gen_uuid();
- line.uuid = uuid;
- line.owner = owner;
- line.ircChanName = iid.get_local();
- line.ircServerName = iid.get_server();
- line.date = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
- line.body = body;
- line.nick = nick;
+ line.col<Uuid>() = uuid;
+ line.col<Owner>() = owner;
+ line.col<IrcChanName>() = chan_name;
+ line.col<IrcServerName>() = server_name;
+ line.col<Date>() = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
+ line.col<Body>() = body;
+ line.col<Nick>() = nick;
- line.update();
+ line.save(Database::db);
return uuid;
}
-std::vector<db::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
+std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
int limit, const std::string& start, const std::string& end)
{
- auto request = litesql::select<db::MucLogLine>(*Database::db,
- db::MucLogLine::Owner == owner &&
- db::MucLogLine::IrcChanName == chan_name &&
- db::MucLogLine::IrcServerName == server);
- request.orderBy(db::MucLogLine::Id, false);
+ auto request = Database::muc_log_lines.select().where() << Database::Owner{} << "=" << owner << \
+ " and " << Database::IrcChanName{} << "=" << chan_name << \
+ " and " << Database::IrcServerName{} << "=" << server;
- if (limit >= 0)
- request.limit(limit);
if (!start.empty())
{
const auto start_time = utils::parse_datetime(start);
if (start_time != -1)
- request.where(db::MucLogLine::Date >= start_time);
+ request << " and " << Database::Date{} << ">=" << start_time;
}
if (!end.empty())
{
const auto end_time = utils::parse_datetime(end);
if (end_time != -1)
- request.where(db::MucLogLine::Date <= end_time);
+ request << " and " << Database::Date{} << "<=" << end_time;
}
- const auto& res = request.all();
- return {res.crbegin(), res.crend()};
+
+ request.order_by() << Id{} << " DESC ";
+
+ if (limit >= 0)
+ request.limit() << limit;
+
+ auto result = request.execute(Database::db);
+
+ return {result.crbegin(), result.crend()};
}
void Database::close()
{
- Database::db.reset(nullptr);
+ sqlite3_close_v2(Database::db);
}
std::string Database::gen_uuid()
@@ 182,5 173,4 @@ std::string Database::gen_uuid()
return uuid_str;
}
-
-#endif
+#endif<
\ No newline at end of file
M src/database/database.hpp => src/database/database.hpp +121 -38
@@ 1,22 1,107 @@
#pragma once
-
#include <biboumi.h>
#ifdef USE_DATABASE
-#include "biboudb.hpp"
-
-#include <memory>
+#include <database/table.hpp>
+#include <database/column.hpp>
+#include <database/count_query.hpp>
-#include <litesql.hpp>
#include <chrono>
+#include <string>
+
+#include <memory>
-class Iid;
class Database
{
-public:
+ public:
using time_point = std::chrono::system_clock::time_point;
+
+ struct Uuid: Column<std::string> { static constexpr auto name = "uuid_";
+ static constexpr auto options = ""; };
+
+ struct Owner: Column<std::string> { static constexpr auto name = "owner_";
+ static constexpr auto options = ""; };
+
+ struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_";
+ static constexpr auto options = ""; };
+
+ struct Channel: Column<std::string> { static constexpr auto name = "channel_";
+ static constexpr auto options = ""; };
+
+ struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_";
+ static constexpr auto options = ""; };
+
+ struct Server: Column<std::string> { static constexpr auto name = "server_";
+ static constexpr auto options = ""; };
+
+ struct Date: Column<time_point::rep> { static constexpr auto name = "date_";
+ static constexpr auto options = ""; };
+
+ struct Body: Column<std::string> { static constexpr auto name = "body_";
+ static constexpr auto options = ""; };
+
+ struct Nick: Column<std::string> { static constexpr auto name = "nick_";
+ static constexpr auto options = ""; };
+
+ struct Pass: Column<std::string> { static constexpr auto name = "pass_";
+ static constexpr auto options = ""; };
+
+ struct Ports: Column<std::string> { static constexpr auto name = "ports_";
+ static constexpr auto options = "";
+ Ports(): Column<std::string>("6667") {} };
+
+ struct TlsPorts: Column<std::string> { static constexpr auto name = "tlsPorts_";
+ static constexpr auto options = "";
+ TlsPorts(): Column<std::string>("6697;6670") {} };
+
+ struct Username: Column<std::string> { static constexpr auto name = "username_";
+ static constexpr auto options = ""; };
+
+ struct Realname: Column<std::string> { static constexpr auto name = "realname_";
+ static constexpr auto options = ""; };
+
+ struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_";
+ static constexpr auto options = ""; };
+
+ struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_";
+ static constexpr auto options = ""; };
+
+ struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_";
+ static constexpr auto options = ""; };
+
+ struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_";
+ static constexpr auto options = ""; };
+
+ struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxHistoryLength_";
+ static constexpr auto options = "";
+ MaxHistoryLength(): Column<int>(20) {} };
+
+ struct RecordHistory: Column<bool> { static constexpr auto name = "recordHistory_";
+ static constexpr auto options = "";
+ RecordHistory(): Column<bool>(true) {}};
+
+ struct VerifyCert: Column<bool> { static constexpr auto name = "verifyCert_";
+ static constexpr auto options = "";
+ VerifyCert(): Column<bool>(true) {} };
+
+ struct Persistent: Column<bool> { static constexpr auto name = "persistent_";
+ static constexpr auto options = "";
+ Persistent(): Column<bool>(false) {} };
+
+ using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>;
+ using MucLogLine = MucLogLineTable::RowType;
+
+ using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory>;
+ using GlobalOptions = GlobalOptionsTable::RowType;
+
+ using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, AfterConnectionCommand, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength>;
+ using IrcServerOptions = IrcServerOptionsTable::RowType;
+
+ using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent>;
+ using IrcChannelOptions = IrcChannelOptionsTable::RowType;
+
Database() = default;
~Database() = default;
@@ 25,42 110,40 @@ public:
Database& operator=(const Database&) = delete;
Database& operator=(Database&&) = delete;
- static void set_verbose(const bool val);
-
- template<typename PersistentType>
- static size_t count()
- {
- return litesql::select<PersistentType>(*Database::db).count();
- }
- /**
- * Return the object from the db. Create it beforehand (with all default
- * values) if it is not already present.
- */
- static db::GlobalOptions get_global_options(const std::string& owner);
- static db::IrcServerOptions get_irc_server_options(const std::string& owner,
+ static GlobalOptions get_global_options(const std::string& owner);
+ static IrcServerOptions get_irc_server_options(const std::string& owner,
const std::string& server);
- static db::IrcChannelOptions get_irc_channel_options(const std::string& owner,
- const std::string& server,
- const std::string& channel);
- static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
- const std::string& server,
- const std::string& channel);
- static db::IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
- const std::string& server,
- const std::string& channel);
- static std::vector<db::MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
- int limit=-1, const std::string& start="", const std::string& end="");
- static std::string store_muc_message(const std::string& owner, const Iid& iid,
- time_point date, const std::string& body, const std::string& nick);
+ static IrcChannelOptions get_irc_channel_options(const std::string& owner,
+ const std::string& server,
+ const std::string& channel);
+ static IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner,
+ const std::string& server,
+ const std::string& channel);
+ static IrcChannelOptions get_irc_channel_options_with_server_and_global_default(const std::string& owner,
+ const std::string& server,
+ const std::string& channel);
+ static std::vector<MucLogLine> get_muc_logs(const std::string& owner, const std::string& chan_name, const std::string& server,
+ int limit=-1, const std::string& start="", const std::string& end="");
+ static std::string store_muc_message(const std::string& owner, const std::string& chan_name, const std::string& server_name,
+ time_point date, const std::string& body, const std::string& nick);
static void close();
- static void open(const std::string& filename, const std::string& db_type="sqlite3");
+ static void open(const std::string& filename);
+ template <typename TableType>
+ static int64_t count(const TableType& table)
+ {
+ CountQuery query{table.get_name()};
+ return query.execute(Database::db);
+ }
+
+ static MucLogLineTable muc_log_lines;
+ static GlobalOptionsTable global_options;
+ static IrcServerOptionsTable irc_server_options;
+ static IrcChannelOptionsTable irc_channel_options;
+ static sqlite3* db;
-private:
+ private:
static std::string gen_uuid();
- static std::unique_ptr<db::BibouDB> db;
};
#endif /* USE_DATABASE */
-
-
A src/database/insert_query.hpp => src/database/insert_query.hpp +129 -0
@@ 0,0 1,129 @@
+#pragma once
+
+#include <database/statement.hpp>
+#include <database/column.hpp>
+#include <database/query.hpp>
+#include <logger/logger.hpp>
+
+#include <type_traits>
+#include <vector>
+#include <string>
+#include <tuple>
+
+#include <sqlite3.h>
+
+template <int N, typename ColumnType, typename... T>
+typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
+actual_bind(Statement& statement, std::vector<std::string>& params, const std::tuple<T...>&)
+{
+ const auto value = params.front();
+ params.erase(params.begin());
+ if (sqlite3_bind_text(statement.get(), N + 1, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) != SQLITE_OK)
+ log_error("Failed to bind ", value, " to param ", N);
+ else
+ log_debug("Bound (not id) [", value, "] to ", N);
+}
+
+template <int N, typename ColumnType, typename... T>
+typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
+actual_bind(Statement& statement, std::vector<std::string>&, const std::tuple<T...>& columns)
+{
+ auto&& column = std::get<Id>(columns);
+ if (column.value != 0)
+ {
+ if (sqlite3_bind_int64(statement.get(), N + 1, static_cast<sqlite3_int64>(column.value)) != SQLITE_OK)
+ log_error("Failed to bind ", column.value, " to id.");
+ }
+ else if (sqlite3_bind_null(statement.get(), N + 1) != SQLITE_OK)
+ log_error("Failed to bind NULL to param ", N);
+ else
+ log_debug("Bound NULL to ", N);
+}
+
+struct InsertQuery: public Query
+{
+ InsertQuery(const std::string& name):
+ Query("INSERT OR REPLACE INTO ")
+ {
+ this->body += name;
+ }
+
+ template <typename... T>
+ void execute(const std::tuple<T...>& columns, sqlite3* db)
+ {
+ auto statement = this->prepare(db);
+ {
+ this->bind_param<0>(columns, statement);
+ if (sqlite3_step(statement.get()) != SQLITE_DONE)
+ log_error("Failed to execute query: ", sqlite3_errmsg(db));
+ }
+ }
+
+ template <int N, typename... T>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ bind_param(const std::tuple<T...>& columns, Statement& statement)
+ {
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
+
+ actual_bind<N, ColumnType>(statement, this->params, columns);
+ this->bind_param<N+1>(columns, statement);
+ }
+
+ template <int N, typename... T>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ bind_param(const std::tuple<T...>&, Statement&)
+ {}
+
+ template <typename... T>
+ void insert_values(const std::tuple<T...>& columns)
+ {
+ this->body += "VALUES (";
+ this->insert_value<0>(columns);
+ this->body += ")";
+ }
+
+ template <int N, typename... T>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ insert_value(const std::tuple<T...>& columns)
+ {
+ this->body += "?";
+ if (N != sizeof...(T) - 1)
+ this->body += ",";
+ this->body += " ";
+ add_param(*this, std::get<N>(columns));
+ this->insert_value<N+1>(columns);
+ }
+ template <int N, typename... T>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ insert_value(const std::tuple<T...>&)
+ { }
+
+ template <typename... T>
+ void insert_col_names(const std::tuple<T...>& columns)
+ {
+ this->body += " (";
+ this->insert_col_name<0>(columns);
+ this->body += ")\n";
+ }
+
+ template <int N, typename... T>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ insert_col_name(const std::tuple<T...>& columns)
+ {
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
+
+ this->body += ColumnType::name;
+
+ if (N < (sizeof...(T) - 1))
+ this->body += ", ";
+
+ this->insert_col_name<N+1>(columns);
+ }
+ template <int N, typename... T>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ insert_col_name(const std::tuple<T...>&)
+ {}
+
+
+ private:
+};
A src/database/query.cpp => src/database/query.cpp +11 -0
@@ 0,0 1,11 @@
+#include <database/query.hpp>
+#include <database/column.hpp>
+
+template <>
+void add_param<Id>(Query&, const Id&)
+{}
+
+void actual_add_param(Query& query, const std::string& val)
+{
+ query.params.push_back(val);
+}
A src/database/query.hpp => src/database/query.hpp +48 -0
@@ 0,0 1,48 @@
+#pragma once
+
+#include <database/statement.hpp>
+
+#include <logger/logger.hpp>
+
+#include <vector>
+#include <string>
+
+#include <sqlite3.h>
+
+struct Query
+{
+ std::string body;
+ std::vector<std::string> params;
+
+ Query(std::string str):
+ body(std::move(str))
+ {}
+
+ Statement prepare(sqlite3* db)
+ {
+ sqlite3_stmt* statement;
+ log_debug(this->body);
+ auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1,
+ &statement, nullptr);
+ if (res != SQLITE_OK)
+ {
+ log_error("Error preparing statement: ", sqlite3_errmsg(db));
+ return nullptr;
+ }
+ return {statement};
+ }
+};
+
+template <typename ColumnType>
+void add_param(Query& query, const ColumnType& column)
+{
+ actual_add_param(query, column.value);
+}
+
+template <typename T>
+void actual_add_param(Query& query, const T& val)
+{
+ query.params.push_back(std::to_string(val));
+}
+
+void actual_add_param(Query& query, const std::string& val);
A src/database/row.hpp => src/database/row.hpp +75 -0
@@ 0,0 1,75 @@
+#pragma once
+
+#include <database/insert_query.hpp>
+#include <logger/logger.hpp>
+
+#include <type_traits>
+
+#include <sqlite3.h>
+
+template <typename ColumnType, typename... T>
+typename std::enable_if<!std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
+update_id(std::tuple<T...>&, sqlite3*)
+{}
+
+template <typename ColumnType, typename... T>
+typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void>::type
+update_id(std::tuple<T...>& columns, sqlite3* db)
+{
+ auto&& column = std::get<ColumnType>(columns);
+ log_debug("Found an autoincrement col.");
+ auto res = sqlite3_last_insert_rowid(db);
+ log_debug("Value is now: ", res);
+ column.value = static_cast<Id::real_type>(res);
+}
+
+template <std::size_t N, typename... T>
+typename std::enable_if<N < sizeof...(T), void>::type
+update_autoincrement_id(std::tuple<T...>& columns, sqlite3* db)
+{
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(columns))>::type;
+ update_id<ColumnType>(columns, db);
+ update_autoincrement_id<N+1>(columns, db);
+}
+
+template <std::size_t N, typename... T>
+typename std::enable_if<N == sizeof...(T), void>::type
+update_autoincrement_id(std::tuple<T...>&, sqlite3*)
+{}
+
+template <typename... T>
+struct Row
+{
+ Row(std::string name):
+ table_name(std::move(name))
+ {}
+
+ template <typename Type>
+ auto& col()
+ {
+ auto&& col = std::get<Type>(this->columns);
+ return col.value;
+ }
+
+ template <typename Type>
+ const auto& col() const
+ {
+ auto&& col = std::get<Type>(this->columns);
+ return col.value;
+ }
+
+ void save(sqlite3* db)
+ {
+ InsertQuery query(this->table_name);
+ query.insert_col_names(this->columns);
+ query.insert_values(this->columns);
+ log_debug(query.body);
+
+ query.execute(this->columns, db);
+
+ update_autoincrement_id<0>(this->columns, db);
+ }
+
+ std::tuple<T...> columns;
+ std::string table_name;
+};
A src/database/select_query.hpp => src/database/select_query.hpp +153 -0
@@ 0,0 1,153 @@
+#pragma once
+
+#include <database/statement.hpp>
+#include <database/query.hpp>
+#include <logger/logger.hpp>
+#include <database/row.hpp>
+
+#include <vector>
+#include <string>
+
+#include <sqlite3.h>
+
+using namespace std::string_literals;
+
+template <typename T>
+typename std::enable_if<std::is_integral<T>::value, sqlite3_int64>::type
+extract_row_value(Statement& statement, const int i)
+{
+ return sqlite3_column_int64(statement.get(), i);
+}
+
+template <typename T>
+typename std::enable_if<std::is_same<std::string, T>::value, std::string>::type
+extract_row_value(Statement& statement, const int i)
+{
+ const auto size = sqlite3_column_bytes(statement.get(), i);
+ const unsigned char* str = sqlite3_column_text(statement.get(), i);
+ std::string result(reinterpret_cast<const char*>(str), static_cast<std::size_t>(size));
+ return result;
+}
+
+template <std::size_t N=0, typename... T>
+typename std::enable_if<N < sizeof...(T), void>::type
+extract_row_values(Row<T...>& row, Statement& statement)
+{
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(row.columns))>::type;
+
+ auto&& column = std::get<N>(row.columns);
+ column.value = static_cast<decltype(column.value)>(extract_row_value<typename ColumnType::real_type>(statement, N));
+
+ extract_row_values<N+1>(row, statement);
+}
+
+template <std::size_t N=0, typename... T>
+typename std::enable_if<N == sizeof...(T), void>::type
+extract_row_values(Row<T...>&, Statement&)
+{}
+
+template <typename... T>
+struct SelectQuery: public Query
+{
+ SelectQuery(std::string table_name):
+ Query("SELECT"),
+ table_name(table_name)
+ {
+ this->insert_col_name<0>();
+ this->body += " from " + this->table_name;
+ }
+
+ template <std::size_t N>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ insert_col_name()
+ {
+ using ColumnsType = std::tuple<T...>;
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnsType>()))>::type;
+
+ this->body += " "s + ColumnType::name;
+
+ if (N < (sizeof...(T) - 1))
+ this->body += ", ";
+
+ this->insert_col_name<N+1>();
+ }
+ template <std::size_t N>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ insert_col_name()
+ {}
+
+ SelectQuery& where()
+ {
+ this->body += " WHERE ";
+ return *this;
+ };
+
+ SelectQuery& order_by()
+ {
+ this->body += " ORDER BY ";
+ return *this;
+ }
+
+ SelectQuery& limit()
+ {
+ this->body += " LIMIT ";
+ return *this;
+ }
+
+ auto execute(sqlite3* db)
+ {
+ auto statement = this->prepare(db);
+ int i = 1;
+ for (const std::string& param: this->params)
+ {
+ if (sqlite3_bind_text(statement.get(), i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK)
+ log_debug("Failed to bind ", param, " to param ", i);
+ else
+ log_debug("Bound ", param, " to ", i);
+
+ i++;
+ }
+ std::vector<Row<T...>> rows;
+ while (sqlite3_step(statement.get()) == SQLITE_ROW)
+ {
+ Row<T...> row(this->table_name);
+ extract_row_values(row, statement);
+ rows.push_back(row);
+ }
+ return rows;
+ }
+
+ const std::string table_name;
+};
+
+template <typename T, typename... ColumnTypes>
+typename std::enable_if<!std::is_integral<T>::value, SelectQuery<ColumnTypes...>&>::type
+operator<<(SelectQuery<ColumnTypes...>& query, const T&)
+{
+ query.body += T::name;
+ return query;
+}
+
+template <typename... ColumnTypes>
+SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const char* str)
+{
+ query.body += str;
+ return query;
+}
+
+template <typename... ColumnTypes>
+SelectQuery<ColumnTypes...>& operator<<(SelectQuery<ColumnTypes...>& query, const std::string& str)
+{
+ query.body += "?";
+ actual_add_param(query, str);
+ return query;
+}
+
+template <typename Integer, typename... ColumnTypes>
+typename std::enable_if<std::is_integral<Integer>::value, SelectQuery<ColumnTypes...>&>::type
+operator<<(SelectQuery<ColumnTypes...>& query, const Integer& i)
+{
+ query.body += "?";
+ actual_add_param(query, i);
+ return query;
+}
A src/database/statement.hpp => src/database/statement.hpp +35 -0
@@ 0,0 1,35 @@
+#pragma once
+
+#include <sqlite3.h>
+
+class Statement
+{
+ public:
+ Statement(sqlite3_stmt* stmt):
+ stmt(stmt) {}
+ ~Statement()
+ {
+ sqlite3_finalize(this->stmt);
+ }
+
+ Statement(const Statement&) = delete;
+ Statement& operator=(const Statement&) = delete;
+ Statement(Statement&& other):
+ stmt(other.stmt)
+ {
+ other.stmt = nullptr;
+ }
+ Statement& operator=(Statement&& other)
+ {
+ this->stmt = other.stmt;
+ other.stmt = nullptr;
+ return *this;
+ }
+ sqlite3_stmt* get()
+ {
+ return this->stmt;
+ }
+
+ private:
+ sqlite3_stmt* stmt;
+};
A src/database/table.hpp => src/database/table.hpp +84 -0
@@ 0,0 1,84 @@
+#pragma once
+
+#include <database/select_query.hpp>
+#include <database/type_to_sql.hpp>
+#include <logger/logger.hpp>
+#include <database/row.hpp>
+
+#include <algorithm>
+#include <string>
+
+template <typename... T>
+class Table
+{
+ static_assert(sizeof...(T) > 0, "Table cannot be empty");
+ using ColumnTypes = std::tuple<T...>;
+
+ public:
+ using RowType = Row<T...>;
+
+ Table(std::string name):
+ name(std::move(name))
+ {}
+
+ void create(sqlite3* db)
+ {
+ std::string res{"CREATE TABLE IF NOT EXISTS "};
+ res += this->name;
+ res += " (\n";
+ this->add_column_create<0>(res);
+ res += ")";
+
+ log_debug(res);
+
+ char* error;
+ const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error);
+ log_debug("result: ", +result);
+ if (result != SQLITE_OK)
+ {
+ log_error("Error executing query: ", error);
+ sqlite3_free(error);
+ }
+ }
+
+ RowType row()
+ {
+ return {this->name};
+ }
+
+ SelectQuery<T...> select()
+ {
+ SelectQuery<T...> select(this->name);
+ return select;
+ }
+
+ const std::string& get_name() const
+ {
+ return this->name;
+ }
+
+ private:
+ template <std::size_t N>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ add_column_create(std::string& str)
+ {
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
+ using RealType = typename ColumnType::real_type;
+ str += ColumnType::name;
+ str += " ";
+ str += TypeToSQLType<RealType>::type;
+ str += " "s + ColumnType::options;
+ if (N != sizeof...(T) - 1)
+ str += ",";
+ str += "\n";
+
+ add_column_create<N+1>(str);
+ }
+
+ template <std::size_t N>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ add_column_create(std::string&)
+ { }
+
+ const std::string name;
+};
A src/database/type_to_sql.cpp => src/database/type_to_sql.cpp +8 -0
@@ 0,0 1,8 @@
+#include <database/type_to_sql.hpp>
+
+template <> const std::string TypeToSQLType<int>::type = "INTEGER";
+template <> const std::string TypeToSQLType<std::size_t>::type = "INTEGER";
+template <> const std::string TypeToSQLType<long>::type = "INTEGER";
+template <> const std::string TypeToSQLType<long long>::type = "INTEGER";
+template <> const std::string TypeToSQLType<bool>::type = "INTEGER";
+template <> const std::string TypeToSQLType<std::string>::type = "TEXT";
A src/database/type_to_sql.hpp => src/database/type_to_sql.hpp +13 -0
@@ 0,0 1,13 @@
+#pragma once
+
+#include <string>
+
+template <typename T>
+struct TypeToSQLType { static const std::string type; };
+
+template <> const std::string TypeToSQLType<int>::type;
+template <> const std::string TypeToSQLType<std::size_t>::type;
+template <> const std::string TypeToSQLType<long>::type;
+template <> const std::string TypeToSQLType<long long>::type;
+template <> const std::string TypeToSQLType<bool>::type;
+template <> const std::string TypeToSQLType<std::string>::type;<
\ No newline at end of file
M src/irc/irc_client.cpp => src/irc/irc_client.cpp +12 -12
@@ 156,11 156,11 @@ IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- std::vector<std::string> ports = utils::split(options.ports, ';', false);
+ std::vector<std::string> ports = utils::split(options.col<Database::Ports>(), ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, false);
# ifdef BOTAN_FOUND
- ports = utils::split(options.tlsPorts, ';', false);
+ ports = utils::split(options.col<Database::TlsPorts>(), ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, true);
# endif // BOTAN_FOUND
@@ 204,7 204,7 @@ void IrcClient::start()
# ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- this->credential_manager.set_trusted_fingerprint(options.trustedFingerprint);
+ this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
# endif
#endif
this->connect(this->hostname, port, tls);
@@ 275,8 275,8 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- if (!options.pass.value().empty())
- this->send_pass_command(options.pass.value());
+ if (!options.col<Database::Pass>().empty())
+ this->send_pass_command(options.col<Database::Pass>());
#endif
this->send_nick_command(this->current_nick);
@@ 284,10 284,10 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
if (Config::get("realname_customization", "true") == "true")
{
- if (!options.username.value().empty())
- this->username = options.username.value();
- if (!options.realname.value().empty())
- this->realname = options.realname.value();
+ if (!options.col<Database::Username>().empty())
+ this->username = options.col<Database::Username>();
+ if (!options.col<Database::Realname>().empty())
+ this->realname = options.col<Database::Realname>();
this->send_user_command(username, realname);
}
else
@@ 894,8 894,8 @@ void IrcClient::on_welcome_message(const IrcMessage& message)
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
- if (!options.afterConnectionCommand.value().empty())
- this->send_raw(options.afterConnectionCommand.value());
+ if (!options.col<Database::AfterConnectionCommand>().empty())
+ this->send_raw(options.col<Database::AfterConnectionCommand>());
#endif
// Install a repeated events to regularly send a PING
TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
@@ 1260,7 1260,7 @@ bool IrcClient::abort_on_invalid_cert() const
{
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname);
- return options.verifyCert.value();
+ return options.col<Database::VerifyCert>();
#endif
return true;
}
M src/main.cpp => src/main.cpp +1 -4
@@ 12,9 12,6 @@
#include <atomic>
#include <csignal>
-#ifdef USE_DATABASE
-# include <litesql.hpp>
-#endif
#include <identd/identd_server.hpp>
@@ 91,7 88,7 @@ int main(int ac, char** av)
#ifdef USE_DATABASE
try {
open_database();
- } catch (const litesql::DatabaseError&) {
+ } catch (...) {
return 1;
}
#endif
M src/utils/reload.cpp => src/utils/reload.cpp +2 -1
@@ 11,6 11,7 @@ void open_database()
#ifdef USE_DATABASE
const auto db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite"));
log_info("Opening database: ", db_filename);
+ Database::close();
Database::open(db_filename);
log_info("database successfully opened.");
#endif
@@ 26,7 27,7 @@ void reload_process()
#ifdef USE_DATABASE
try {
open_database();
- } catch (const litesql::DatabaseError&) {
+ } catch (...) {
log_warning("Re-using the previous database.");
}
#endif
M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +43 -45
@@ 130,7 130,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{
XmlSubNode value(max_histo_length, "value");
- value.set_inner(std::to_string(options.maxHistoryLength.value()));
+ value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>()));
}
XmlSubNode record_history(x, "field");
@@ 142,7 142,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{
XmlSubNode value(record_history, "value");
value.set_name("value");
- if (options.recordHistory.value())
+ if (options.col<Database::RecordHistory>())
value.set_inner("true");
else
value.set_inner("false");
@@ 164,18 164,18 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session,
if (field->get_tag("var") == "max_history_length" &&
value && !value->get_inner().empty())
- options.maxHistoryLength = value->get_inner();
+ options.col<Database::MaxHistoryLength>() = atoi(value->get_inner().data());
else if (field->get_tag("var") == "record_history" &&
value && !value->get_inner().empty())
{
- options.recordHistory = to_bool(value->get_inner());
+ options.col<Database::RecordHistory>() = to_bool(value->get_inner());
Bridge* bridge = biboumi_component.find_user_bridge(owner.bare());
if (bridge)
- bridge->set_record_history(options.recordHistory.value());
+ bridge->set_record_history(options.col<Database::RecordHistory>());
}
}
- options.update();
+ options.save(Database::db);
command_node.delete_all_children();
XmlSubNode note(command_node, "note");
@@ 211,8 211,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
ports["type"] = "text-multi";
ports["label"] = "Ports";
ports["desc"] = "List of ports to try, without TLS. Defaults: 6667.";
- auto vals = utils::split(options.ports.value(), ';', false);
- for (const auto& val: vals)
+ for (const auto& val: utils::split(options.col<Database::Ports>(), ';', false))
{
XmlSubNode ports_value(ports, "value");
ports_value.set_inner(val);
@@ 224,8 223,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
tls_ports["type"] = "text-multi";
tls_ports["label"] = "TLS ports";
tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670.";
- vals = utils::split(options.tlsPorts.value(), ';', false);
- for (const auto& val: vals)
+ for (const auto& val: utils::split(options.col<Database::TlsPorts>(), ';', false))
{
XmlSubNode tls_ports_value(tls_ports, "value");
tls_ports_value.set_inner(val);
@@ 237,7 235,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
verify_cert["label"] = "Verify certificate";
verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid";
XmlSubNode verify_cert_value(verify_cert, "value");
- if (options.verifyCert.value())
+ if (options.col<Database::VerifyCert>())
verify_cert_value.set_inner("true");
else
verify_cert_value.set_inner("false");
@@ 246,10 244,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
fingerprint["var"] = "fingerprint";
fingerprint["type"] = "text-single";
fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust.";
- if (!options.trustedFingerprint.value().empty())
+ if (!options.col<Database::TrustedFingerprint>().empty())
{
XmlSubNode fingerprint_value(fingerprint, "value");
- fingerprint_value.set_inner(options.trustedFingerprint.value());
+ fingerprint_value.set_inner(options.col<Database::TrustedFingerprint>());
}
#endif
@@ 258,10 256,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
pass["type"] = "text-private";
pass["label"] = "Server password";
pass["desc"] = "Will be used in a PASS command when connecting";
- if (!options.pass.value().empty())
+ if (!options.col<Database::Pass>().empty())
{
XmlSubNode pass_value(pass, "value");
- pass_value.set_inner(options.pass.value());
+ pass_value.set_inner(options.col<Database::Pass>());
}
XmlSubNode after_cnt_cmd(x, "field");
@@ 269,10 267,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
after_cnt_cmd["type"] = "text-single";
after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server.";
after_cnt_cmd["label"] = "After-connection IRC command";
- if (!options.afterConnectionCommand.value().empty())
+ if (!options.col<Database::AfterConnectionCommand>().empty())
{
XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value");
- after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value());
+ after_cnt_cmd_value.set_inner(options.col<Database::AfterConnectionCommand>());
}
if (Config::get("realname_customization", "true") == "true")
@@ 281,20 279,20 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
username["var"] = "username";
username["type"] = "text-single";
username["label"] = "Username";
- if (!options.username.value().empty())
+ if (!options.col<Database::Username>().empty())
{
XmlSubNode username_value(username, "value");
- username_value.set_inner(options.username.value());
+ username_value.set_inner(options.col<Database::Username>());
}
XmlSubNode realname(x, "field");
realname["var"] = "realname";
realname["type"] = "text-single";
realname["label"] = "Realname";
- if (!options.realname.value().empty())
+ if (!options.col<Database::Realname>().empty())
{
XmlSubNode realname_value(realname, "value");
- realname_value.set_inner(options.realname.value());
+ realname_value.set_inner(options.col<Database::Realname>());
}
}
@@ 303,10 301,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
encoding_out["type"] = "text-single";
encoding_out["desc"] = "The encoding used when sending messages to the IRC server.";
encoding_out["label"] = "Out encoding";
- if (!options.encodingOut.value().empty())
+ if (!options.col<Database::EncodingOut>().empty())
{
XmlSubNode encoding_out_value(encoding_out, "value");
- encoding_out_value.set_inner(options.encodingOut.value());
+ encoding_out_value.set_inner(options.col<Database::EncodingOut>());
}
XmlSubNode encoding_in(x, "field");
@@ 314,10 312,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
encoding_in["type"] = "text-single";
encoding_in["desc"] = "The encoding used to decode message received from the IRC server.";
encoding_in["label"] = "In encoding";
- if (!options.encodingIn.value().empty())
+ if (!options.col<Database::EncodingIn>().empty())
{
XmlSubNode encoding_in_value(encoding_in, "value");
- encoding_in_value.set_inner(options.encodingIn.value());
+ encoding_in_value.set_inner(options.col<Database::EncodingIn>());
}
}
@@ 342,7 340,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
std::string ports;
for (const auto& val: values)
ports += val->get_inner() + ";";
- options.ports = ports;
+ options.col<Database::Ports>() = ports;
}
#ifdef BOTAN_FOUND
@@ 351,31 349,31 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
std::string ports;
for (const auto& val: values)
ports += val->get_inner() + ";";
- options.tlsPorts = ports;
+ options.col<Database::TlsPorts>() = ports;
}
else if (field->get_tag("var") == "verify_cert" && value
&& !value->get_inner().empty())
{
auto val = to_bool(value->get_inner());
- options.verifyCert = val;
+ options.col<Database::VerifyCert>() = val;
}
else if (field->get_tag("var") == "fingerprint" && value &&
!value->get_inner().empty())
{
- options.trustedFingerprint = value->get_inner();
+ options.col<Database::TrustedFingerprint>() = value->get_inner();
}
#endif // BOTAN_FOUND
else if (field->get_tag("var") == "pass" &&
value && !value->get_inner().empty())
- options.pass = value->get_inner();
+ options.col<Database::Pass>() = value->get_inner();
else if (field->get_tag("var") == "after_connect_command" &&
value && !value->get_inner().empty())
- options.afterConnectionCommand = value->get_inner();
+ options.col<Database::AfterConnectionCommand>() = value->get_inner();
else if (field->get_tag("var") == "username" &&
value && !value->get_inner().empty())
@@ 383,24 381,24 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
auto username = value->get_inner();
// The username must not contain spaces
std::replace(username.begin(), username.end(), ' ', '_');
- options.username = username;
+ options.col<Database::Username>() = username;
}
else if (field->get_tag("var") == "realname" &&
value && !value->get_inner().empty())
- options.realname = value->get_inner();
+ options.col<Database::Realname>() = value->get_inner();
else if (field->get_tag("var") == "encoding_out" &&
value && !value->get_inner().empty())
- options.encodingOut = value->get_inner();
+ options.col<Database::EncodingOut>() = value->get_inner();
else if (field->get_tag("var") == "encoding_in" &&
value && !value->get_inner().empty())
- options.encodingIn = value->get_inner();
+ options.col<Database::EncodingIn>() = value->get_inner();
}
- options.update();
+ options.save(Database::db);
command_node.delete_all_children();
XmlSubNode note(command_node, "note");
@@ 441,10 439,10 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
encoding_out["type"] = "text-single";
encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel";
encoding_out["label"] = "Out encoding";
- if (!options.encodingOut.value().empty())
+ if (!options.col<Database::EncodingOut>().empty())
{
XmlSubNode encoding_out_value(encoding_out, "value");
- encoding_out_value.set_inner(options.encodingOut.value());
+ encoding_out_value.set_inner(options.col<Database::EncodingOut>());
}
XmlSubNode encoding_in(x, "field");
@@ 452,10 450,10 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
encoding_in["type"] = "text-single";
encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel";
encoding_in["label"] = "In encoding";
- if (!options.encodingIn.value().empty())
+ if (!options.col<Database::EncodingIn>().empty())
{
XmlSubNode encoding_in_value(encoding_in, "value");
- encoding_in_value.set_inner(options.encodingIn.value());
+ encoding_in_value.set_inner(options.col<Database::EncodingIn>());
}
XmlSubNode persistent(x, "field");
@@ 466,7 464,7 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
{
XmlSubNode value(persistent, "value");
value.set_name("value");
- if (options.persistent.value())
+ if (options.col<Database::Persistent>())
value.set_inner("true");
else
value.set_inner("false");
@@ 510,18 508,18 @@ bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& reque
if (field->get_tag("var") == "encoding_out" &&
value && !value->get_inner().empty())
- options.encodingOut = value->get_inner();
+ options.col<Database::EncodingOut>() = value->get_inner();
else if (field->get_tag("var") == "encoding_in" &&
value && !value->get_inner().empty())
- options.encodingIn = value->get_inner();
+ options.col<Database::EncodingIn>() = value->get_inner();
else if (field->get_tag("var") == "persistent" &&
value)
- options.persistent = to_bool(value->get_inner());
+ options.col<Database::Persistent>() = to_bool(value->get_inner());
}
- options.update();
+ options.save(Database::db);
}
return true;
}
M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +7 -9
@@ 18,8 18,6 @@
#include <biboumi.h>
-#include <uuid/uuid.h>
-
#ifdef SYSTEMD_FOUND
# include <systemd/sd-daemon.h>
#endif
@@ 648,9 646,9 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
limit = 100;
}
const auto lines = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(), limit, start, end);
- for (const db::MucLogLine& line: lines)
+ for (const Database::MucLogLine& line: lines)
{
- if (!line.nick.value().empty())
+ if (!line.col<Database::Nick>().empty())
this->send_archived_message(line, to.full(), from.full(), query_id);
}
this->send_iq_result_full_jid(id, from.full(), to.full());
@@ 659,7 657,7 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
return false;
}
-void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
+void BiboumiComponent::send_archived_message(const Database::MucLogLine& log_line, const std::string& from, const std::string& to,
const std::string& queryid)
{
Stanza message("message");
@@ 671,22 669,22 @@ void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, con
result["xmlns"] = MAM_NS;
if (!queryid.empty())
result["queryid"] = queryid;
- result["id"] = log_line.uuid.value();
+ result["id"] = log_line.col<Database::Uuid>();
XmlSubNode forwarded(result, "forwarded");
forwarded["xmlns"] = FORWARD_NS;
XmlSubNode delay(forwarded, "delay");
delay["xmlns"] = DELAY_NS;
- delay["stamp"] = utils::to_string(log_line.date.value().timeStamp());
+ delay["stamp"] = utils::to_string(log_line.col<Database::Date>());
XmlSubNode submessage(forwarded, "message");
submessage["xmlns"] = CLIENT_NS;
- submessage["from"] = from + "/" + log_line.nick.value();
+ submessage["from"] = from + "/" + log_line.col<Database::Nick>();
submessage["type"] = "groupchat";
XmlSubNode body(submessage, "body");
- body.set_inner(log_line.body.value());
+ body.set_inner(log_line.col<Database::Body>());
}
this->send_stanza(message);
}
M src/xmpp/biboumi_component.hpp => src/xmpp/biboumi_component.hpp +2 -2
@@ 1,6 1,6 @@
#pragma once
-
+#include <database/database.hpp>
#include <xmpp/xmpp_component.hpp>
#include <xmpp/jid.hpp>
@@ 96,7 96,7 @@ public:
#ifdef USE_DATABASE
bool handle_mam_request(const Stanza& stanza);
- void send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
+ void send_archived_message(const Database::MucLogLine& log_line, const std::string& from, const std::string& to,
const std::string& queryid);
bool handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id);
bool handle_room_configuration_form(const XmlNode& query, const std::string& from, const Jid& to, const std::string& id);
M tests/database.cpp => tests/database.cpp +22 -24
@@ 8,24 8,22 @@ TEST_CASE("Database")
{
#ifdef USE_DATABASE
Database::open(":memory:");
- Database::set_verbose(false);
SECTION("Basic retrieve and update")
{
auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
- o.update();
+ o.save(Database::db);
auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com");
auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com");
// b does not yet exist in the db, the object is created but not yet
// inserted
- CHECK(1 == Database::count<db::IrcServerOptions>());
+ CHECK(1 == Database::count(Database::irc_server_options));
- b.update();
- CHECK(2 == Database::count<db::IrcServerOptions>());
+ b.save(Database::db);
+ CHECK(2 == Database::count(Database::irc_server_options));
- CHECK(b.pass == "");
- CHECK(b.pass.value() == "");
+ CHECK(b.col<Database::Pass>() == "");
}
SECTION("channel options")
@@ 33,16 31,16 @@ TEST_CASE("Database")
Config::set("db_name", ":memory:");
auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo");
- CHECK(o.encodingIn == "");
- o.encodingIn = "ISO-8859-1";
- o.update();
+ CHECK(o.col<Database::EncodingIn>() == "");
+ o.col<Database::EncodingIn>() = "ISO-8859-1";
+ o.save(Database::db);
auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo");
- CHECK(o.encodingIn == "ISO-8859-1");
+ CHECK(o.col<Database::EncodingIn>() == "ISO-8859-1");
}
SECTION("Channel options with server default")
{
- const std::string owner{"zouzou@example.com"};
+ const std::string owner{"CACA@example.com"};
const std::string server{"irc.example.com"};
const std::string chan1{"#foo"};
@@ 51,43 49,43 @@ TEST_CASE("Database")
GIVEN("An option defined for the channel but not the server")
{
- c.encodingIn = "channelEncoding";
- c.update();
+ c.col<Database::EncodingIn>() = "channelEncoding";
+ c.save(Database::db);
WHEN("we fetch that option")
{
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the channel option")
- CHECK(r.encodingIn == "channelEncoding");
+ CHECK(r.col<Database::EncodingIn>() == "channelEncoding");
}
}
GIVEN("An option defined for the server but not the channel")
{
- s.encodingIn = "serverEncoding";
- s.update();
+ s.col<Database::EncodingIn>() = "serverEncoding";
+ s.save(Database::db);
WHEN("we fetch that option")
{
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the server option")
- CHECK(r.encodingIn == "serverEncoding");
+ CHECK(r.col<Database::EncodingIn>() == "serverEncoding");
}
}
GIVEN("An option defined for both the server and the channel")
{
- s.encodingIn = "serverEncoding";
- s.update();
- c.encodingIn = "channelEncoding";
- c.update();
+ s.col<Database::EncodingIn>() = "serverEncoding";
+ s.save(Database::db);
+ c.col<Database::EncodingIn>() = "channelEncoding";
+ c.save(Database::db);
WHEN("we fetch that option")
{
auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1);
THEN("we get the channel option")
- CHECK(r.encodingIn == "channelEncoding");
+ CHECK(r.col<Database::EncodingIn>() == "channelEncoding");
}
WHEN("we fetch that option, with no channel specified")
{
auto r = Database::get_irc_channel_options_with_server_default(owner, server, "");
THEN("we get the server option")
- CHECK(r.encodingIn == "serverEncoding");
+ CHECK(r.col<Database::EncodingIn>() == "serverEncoding");
}
}
}
M tests/end_to_end/__main__.py => tests/end_to_end/__main__.py +1 -1
@@ 2417,7 2417,7 @@ if __name__ == '__main__':
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='username']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='realname']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']",
- "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='ISO-8859-1']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']",
"/iq/commands:command/commands:actions/commands:next",
),
after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))