summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlouiz’ <louiz@louiz.org>2017-06-28 14:42:33 +0200
committerlouiz’ <louiz@louiz.org>2017-06-28 14:42:33 +0200
commitc5407cf8ce6add6f3534df41398235e93b605ab3 (patch)
treef62cccb758dde48270cc24257df36bd3f5f89cf8
parent13a1ab1878fd6312aea485ded3f5bad59c36f17f (diff)
parentb71ca15a0f9114db38eec23b49d1489a2ff1d0ca (diff)
downloadbiboumi-c5407cf8ce6add6f3534df41398235e93b605ab3.tar.gz
biboumi-c5407cf8ce6add6f3534df41398235e93b605ab3.tar.bz2
biboumi-c5407cf8ce6add6f3534df41398235e93b605ab3.tar.xz
biboumi-c5407cf8ce6add6f3534df41398235e93b605ab3.zip
Merge branch 'master' into debian
-rw-r--r--.gitlab-ci.yml168
-rw-r--r--CHANGELOG.rst8
-rw-r--r--CMakeLists.txt37
-rw-r--r--CONTRIBUTING.rst6
-rw-r--r--INSTALL.rst13
-rw-r--r--cmake/Modules/FindLITESQL.cmake76
-rw-r--r--cmake/Modules/FindSQLITE3.cmake43
-rw-r--r--database/database.xml70
-rw-r--r--doc/biboumi.1.rst7
-rw-r--r--docker/biboumi-test/alpine/Dockerfile49
-rw-r--r--docker/biboumi-test/alpine/Dockerfile.base51
-rw-r--r--docker/biboumi-test/debian/Dockerfile56
-rw-r--r--docker/biboumi-test/debian/Dockerfile.base57
-rw-r--r--docker/biboumi-test/fedora/Dockerfile58
-rw-r--r--docker/biboumi-test/fedora/Dockerfile.base59
-rw-r--r--docker/biboumi/Dockerfile5
-rw-r--r--packaging/biboumi.spec.cmake12
-rw-r--r--src/biboumi.h.cmake (renamed from biboumi.h.cmake)0
-rw-r--r--src/bridge/bridge.cpp22
-rw-r--r--src/database/column.hpp17
-rw-r--r--src/database/count_query.hpp35
-rw-r--r--src/database/database.cpp219
-rw-r--r--src/database/database.hpp162
-rw-r--r--src/database/insert_query.hpp129
-rw-r--r--src/database/query.cpp34
-rw-r--r--src/database/query.hpp90
-rw-r--r--src/database/row.hpp75
-rw-r--r--src/database/select_query.hpp127
-rw-r--r--src/database/statement.hpp35
-rw-r--r--src/database/table.cpp25
-rw-r--r--src/database/table.hpp127
-rw-r--r--src/database/type_to_sql.cpp9
-rw-r--r--src/database/type_to_sql.hpp16
-rw-r--r--src/irc/irc_client.cpp24
-rw-r--r--src/main.cpp5
-rw-r--r--src/utils/optional_bool.hpp35
-rw-r--r--src/utils/reload.cpp2
-rw-r--r--src/xmpp/biboumi_adhoc_commands.cpp141
-rw-r--r--src/xmpp/biboumi_adhoc_commands.hpp2
-rw-r--r--src/xmpp/biboumi_component.cpp18
-rw-r--r--src/xmpp/biboumi_component.hpp4
-rw-r--r--tests/database.cpp50
-rw-r--r--tests/end_to_end/__main__.py6
-rw-r--r--tests/end_to_end/ircd.conf2
44 files changed, 1399 insertions, 787 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index c616f89..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,168 +0,0 @@
-stages:
- - build
- - test
- - packaging
-
-before_script:
- - uname -a
- - locale
- - whoami
- - rm -rf build/
- - mkdir build/
- - cd build
-
-variables:
- COMPILER: "g++"
- BUILD_TYPE: "Debug"
- BOTAN: "-DWITH_BOTAN=1"
- UDNS: "-DWITH_UDNS=1"
- SYSTEMD: "-DWITH_SYSTEMD=1"
- LIBIDN: "-DWITH_LIBIDN=1"
- LITESQL: "-DWITH_LITESQL=1"
-.template:basic_build: &basic_build
- stage: build
- tags:
- - docker
- image: docker.louiz.org/biboumi-test-fedora:latest
- 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}
- - make biboumi -j$(nproc || echo 1)
- - make check -j$(nproc || echo 1)
-
-build:1:
- variables:
- BOTAN: "-DWITHOUT_BOTAN=1"
- <<: *basic_build
-
-build:2:
- variables:
- UDNS: "-DWITHOUT_UDNS=1"
- <<: *basic_build
-
-build:3:
- variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
- <<: *basic_build
-
-build:4:
- variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
- BOTAN: "-DWITHOUT_BOTAN=1"
- <<: *basic_build
-
-build:5:
- variables:
- LITESQL: "-DWITHOUT_LITESQL=1"
- UDNS: "-DWITHOUT_UDNS=1"
- <<: *basic_build
-
-build:6:
- variables:
- BOTAN: "-DWITHOUT_BOTAN=1"
- UDNS: "-DWITHOUT_UDNS=1"
- <<: *basic_build
-
-build:6:
- variables:
- LIBIDN: "-DWITHOUT_LIBIDN=1"
- UDNS: "-DWITHOUT_UDNS=1"
- <<: *basic_build
-
-.template:basic_test: &basic_test
- stage: test
- tags:
- - docker
- script:
- - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
- - make biboumi -j$(nproc || echo 1)
- - make coverage_check -j$(nproc || echo 1)
- - make coverage_e2e -j$(nproc || echo 1)
- - make coverage
- - bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -f ./coverage_e2e.info -F integration
- - bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -f ./coverage_test_suite.info -F unittests
- artifacts:
- paths:
- - build/coverage_test_suite/
- - build/coverage_e2e/
- - build/coverage_total/
- when: always
- name: $CI_PROJECT_NAME-test-$CI_BUILD_ID
-
-test:debian:
- image: docker.louiz.org/biboumi-test-debian:latest
- <<: *basic_test
-
-test:fedora:
- image: docker.louiz.org/biboumi-test-fedora:latest
- <<: *basic_test
-
-test:freebsd:
- only:
- - master@louiz/biboumi
- tags:
- - freebsd
- variables:
- SYSTEMD: "-DWITHOUT_SYSTEMD=1"
- stage: test
- script:
- - cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
- - make biboumi
- - make check
- - make e2e
-
-test:coverity:
- stage: test
- only:
- - master@louiz/biboumi
- tags:
- - docker
- image: docker.louiz.org/biboumi-test-fedora:latest
- allow_failure: true
- when: manual
- script:
- - export PATH=$PATH:~/coverity/bin
- - cmake .. -DWITHOUT_SYSTEMD=1
- - cov-build --dir cov-int make biboumi test_suite -j$(nproc || echo 1)
- - tar czvf biboumi_coverity.tgz cov-int
- - curl --form token=$COVERITY_TOKEN --form email=louiz@louiz.org --form file=@biboumi_coverity.tgz --form version="$(git rev-parse --short HEAD)" --form description="Automatic submission by gitlab-ci" https://scan.coverity.com/builds?project=louiz%2Fbiboumi
-
-packaging:rpm:
- stage: packaging
- only:
- - master@louiz/biboumi
- tags:
- - docker
- image: docker.louiz.org/biboumi-test-fedora:latest
- script:
- - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${UDNS} ${SYSTEMD} ${LIBIDN} ${LITESQL}
- - make rpm -j$(nproc || echo 1)
- artifacts:
- paths:
- - build/rpmbuild/RPMS
- - build/rpmbuild/SRPMS
- when: always
- name: $CI_PROJECT_NAME-rpm-$CI_BUILD_ID
-
-packaging:deb:
- stage: packaging
- only:
- - master@louiz/biboumi
- - debian@louiz/biboumi
- tags:
- - docker
- image: docker.louiz.org/packaging-debian:latest
- before_script: []
- script:
- - git checkout debian
- - git merge --no-commit --no-ff master
- - mk-build-deps
- - apt update -y
- - apt install -y ./biboumi-build-deps*.deb
- - debuild -b -us -uc
- - mv ../*.deb .
- - mv ../*.build .
- - mv ../*.buildinfo .
- artifacts:
- untracked: true
- name: $CI_PROJECT_NAME-deb-$CI_BUILD_ID
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5709483..c8cddfe 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,11 @@
+Version 6.0
+===========
+
+ - The LiteSQL dependency was removed. Only libsqlite3 is now necessary
+ to work with the database.
+ - The RecordHistory option can now also be configured for each IRC channel,
+ individually.
+
Version 5.0 - 2017-05-24
========================
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 139f85a..596d277 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.0)
project(biboumi)
-set(${PROJECT_NAME}_VERSION_MAJOR 5)
+set(${PROJECT_NAME}_VERSION_MAJOR 6)
set(${PROJECT_NAME}_VERSION_MINOR 0)
-set(${PROJECT_NAME}_VERSION_SUFFIX "")
+set(${PROJECT_NAME}_VERSION_SUFFIX "~dev")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
@@ -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)
@@ -432,4 +423,4 @@ mark_as_advanced(HAS_PUT_TIME)
configure_file(unit/biboumi.service.cmake biboumi.service)
configure_file(packaging/biboumi.spec.cmake biboumi.spec)
-configure_file(biboumi.h.cmake src/biboumi.h)
+configure_file(src/biboumi.h.cmake src/biboumi.h)
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 74459d1..8df4899 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -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
diff --git a/INSTALL.rst b/INSTALL.rst
index 6cd85d2..5bb0ca8 100644
--- a/INSTALL.rst
+++ b/INSTALL.rst
@@ -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/
diff --git a/cmake/Modules/FindLITESQL.cmake b/cmake/Modules/FindLITESQL.cmake
deleted file mode 100644
index 2d3b073..0000000
--- a/cmake/Modules/FindLITESQL.cmake
+++ /dev/null
@@ -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()
diff --git a/cmake/Modules/FindSQLITE3.cmake b/cmake/Modules/FindSQLITE3.cmake
new file mode 100644
index 0000000..2861b37
--- /dev/null
+++ b/cmake/Modules/FindSQLITE3.cmake
@@ -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
diff --git a/database/database.xml b/database/database.xml
deleted file mode 100644
index e641fdf..0000000
--- a/database/database.xml
+++ /dev/null
@@ -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>
diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst
index 5396015..4a2225e 100644
--- a/doc/biboumi.1.rst
+++ b/doc/biboumi.1.rst
@@ -358,7 +358,7 @@ History
-------
Public channel messages are saved into archives, inside the database, unless
-the `record_history` option is set to false for that user `Ad-hoc commands`.
+the `record_history` option is set to false by that user (see `Ad-hoc commands`).
Private messages (messages that are sent directly to a nickname, not a
channel) are never stored in the database. When a channel is joined, biboumi
sends the `max_history_length` messages found in the database as the MUC
@@ -631,6 +631,11 @@ On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com)
the archiving of messages is enabled for this room, the client will
receive the messages that where sent in this channel. This option can be
used to make biboumi act as an IRC bouncer.
+ * Record History: whether or not history messages should be saved in
+ the database, for this specific channel. If the value is “unset” (the
+ default), then the value configured globally is used. This option is there,
+ for example, to be able to enable history recording globally while disabling
+ it for a few specific “private” channels.
Raw IRC messages
----------------
diff --git a/docker/biboumi-test/alpine/Dockerfile b/docker/biboumi-test/alpine/Dockerfile
index ab288b6..f97c58c 100644
--- a/docker/biboumi-test/alpine/Dockerfile
+++ b/docker/biboumi-test/alpine/Dockerfile
@@ -1,10 +1,53 @@
# 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\
+ 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
diff --git a/docker/biboumi-test/alpine/Dockerfile.base b/docker/biboumi-test/alpine/Dockerfile.base
deleted file mode 100644
index dffa1d1..0000000
--- a/docker/biboumi-test/alpine/Dockerfile.base
+++ /dev/null
@@ -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
diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile
index b811ea4..232a585 100644
--- a/docker/biboumi-test/debian/Dockerfile
+++ b/docker/biboumi-test/debian/Dockerfile
@@ -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
diff --git a/docker/biboumi-test/debian/Dockerfile.base b/docker/biboumi-test/debian/Dockerfile.base
deleted file mode 100644
index f5d061b..0000000
--- a/docker/biboumi-test/debian/Dockerfile.base
+++ /dev/null
@@ -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
diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile
index 45dbe76..384fd51 100644
--- a/docker/biboumi-test/fedora/Dockerfile
+++ b/docker/biboumi-test/fedora/Dockerfile
@@ -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
diff --git a/docker/biboumi-test/fedora/Dockerfile.base b/docker/biboumi-test/fedora/Dockerfile.base
deleted file mode 100644
index 20984a2..0000000
--- a/docker/biboumi-test/fedora/Dockerfile.base
+++ /dev/null
@@ -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
diff --git a/docker/biboumi/Dockerfile b/docker/biboumi/Dockerfile
index d27421b..67f0f41 100644
--- a/docker/biboumi/Dockerfile
+++ b/docker/biboumi/Dockerfile
@@ -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
diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake
index dccd2f9..d28c840 100644
--- a/packaging/biboumi.spec.cmake
+++ b/packaging/biboumi.spec.cmake
@@ -11,6 +11,7 @@ BuildRequires: libidn-devel
BuildRequires: expat-devel
BuildRequires: libuuid-devel
BuildRequires: systemd-devel
+BuildRequires: sqlite-devel
BuildRequires: cmake
BuildRequires: systemd
BuildRequires: pandoc
@@ -37,7 +38,8 @@ cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \
-DPOLLER=EPOLL \
-DWITHOUT_BOTAN=1 \
-DWITH_SYSTEMD=1 \
- -DWITH_LIBIDN=1
+ -DWITH_LIBIDN=1 \
+ -DWITH_SQLITE3=1
make %{?_smp_mflags}
@@ -59,7 +61,13 @@ make check %{?_smp_mflags}
%changelog
-* Wed May 24 Le Coz Florent <louiz@louiz.org> - 5.0-1
+* ${RPM_DATE} Le Coz Florent <louiz@louiz.org> - ${RPM_VERSION}-1
+- Build latest git revision
+
+* Wed Jun 14 2017 Le Coz Florent <louiz@louiz.org> - 6.0-1
+ Enable database support by building with sqlite3
+
+* Wed May 24 2017 Le Coz Florent <louiz@louiz.org> - 5.0-1
- Update to version 5.0
* Wed May 2 2017 Le Coz Florent <louiz@louiz.org> - 4.3-1
diff --git a/biboumi.h.cmake b/src/biboumi.h.cmake
index 1ad9a40..1ad9a40 100644
--- a/biboumi.h.cmake
+++ b/src/biboumi.h.cmake
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index 4a41b50..23ecfe9 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -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
diff --git a/src/database/column.hpp b/src/database/column.hpp
new file mode 100644
index 0000000..111f9ca
--- /dev/null
+++ b/src/database/column.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <cstddef>
+
+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"; };
diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp
new file mode 100644
index 0000000..b7bbf51
--- /dev/null
+++ b/src/database/count_query.hpp
@@ -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;
+ }
+};
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 9f310da..92f7682 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -2,175 +2,185 @@
#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
+ // Try to open the specified database.
+ // Close and replace the previous database pointer if it succeeded. If it did
+ // not, just leave things untouched
+ sqlite3* new_db;
+ auto res = sqlite3_open_v2(filename.data(), &new_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
+ if (res != SQLITE_OK)
{
- 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;
+ log_error("Failed to open database file ", filename, ": ", sqlite3_errmsg(Database::db));
+ throw std::runtime_error("");
}
+ Database::close();
+ Database::db = new_db;
+ Database::muc_log_lines.create(Database::db);
+ Database::muc_log_lines.upgrade(Database::db);
+ Database::global_options.create(Database::db);
+ Database::global_options.upgrade(Database::db);
+ Database::irc_server_options.create(Database::db);
+ Database::irc_server_options.upgrade(Database::db);
+ Database::irc_channel_options.create(Database::db);
+ Database::irc_channel_options.upgrade(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();
+ request.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();
+ request.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();
+ request.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.col<EncodingOut>() = get_first_non_empty(coptions.col<EncodingOut>(),
+ soptions.col<EncodingOut>());
- coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
- soptions.maxHistoryLength.value(),
- goptions.maxHistoryLength.value());
+ 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();
+ request.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);
+ Database::db = nullptr;
}
std::string Database::gen_uuid()
@@ -182,5 +192,4 @@ std::string Database::gen_uuid()
return uuid_str;
}
-
-#endif
+#endif \ No newline at end of file
diff --git a/src/database/database.hpp b/src/database/database.hpp
index b08a175..28b6b1b 100644
--- a/src/database/database.hpp
+++ b/src/database/database.hpp
@@ -1,22 +1,112 @@
#pragma once
-
#include <biboumi.h>
#ifdef USE_DATABASE
-#include "biboudb.hpp"
+#include <database/table.hpp>
+#include <database/column.hpp>
+#include <database/count_query.hpp>
-#include <memory>
+#include <utils/optional_bool.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 RecordHistoryOptional: Column<OptionalBool> { static constexpr auto name = "recordHistory_";
+ static constexpr auto options = ""; };
+
+ 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, RecordHistoryOptional>;
+ using IrcChannelOptions = IrcChannelOptionsTable::RowType;
+
Database() = default;
~Database() = default;
@@ -25,42 +115,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 */
-
-
diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp
new file mode 100644
index 0000000..9e410ce
--- /dev/null
+++ b/src/database/insert_query.hpp
@@ -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(columns, statement);
+ if (sqlite3_step(statement.get()) != SQLITE_DONE)
+ log_error("Failed to execute query: ", sqlite3_errmsg(db));
+ }
+ }
+
+ template <int N=0, 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=0, 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(columns);
+ this->body += ")";
+ }
+
+ template <int N=0, 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=0, 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(columns);
+ this->body += ")\n";
+ }
+
+ template <int N=0, 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=0, typename... T>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ insert_col_name(const std::tuple<T...>&)
+ {}
+
+
+ private:
+};
diff --git a/src/database/query.cpp b/src/database/query.cpp
new file mode 100644
index 0000000..ba63a92
--- /dev/null
+++ b/src/database/query.cpp
@@ -0,0 +1,34 @@
+#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);
+}
+
+void actual_add_param(Query& query, const OptionalBool& val)
+{
+ if (!val.is_set)
+ query.params.push_back("0");
+ else if (val.value)
+ query.params.push_back("1");
+ else
+ query.params.push_back("-1");
+}
+
+Query& operator<<(Query& query, const char* str)
+{
+ query.body += str;
+ return query;
+}
+
+Query& operator<<(Query& query, const std::string& str)
+{
+ query.body += "?";
+ actual_add_param(query, str);
+ return query;
+}
diff --git a/src/database/query.hpp b/src/database/query.hpp
new file mode 100644
index 0000000..f103fe9
--- /dev/null
+++ b/src/database/query.hpp
@@ -0,0 +1,90 @@
+#pragma once
+
+#include <utils/optional_bool.hpp>
+#include <database/statement.hpp>
+#include <database/column.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* stmt;
+ log_debug(this->body);
+ auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1,
+ &stmt, nullptr);
+ if (res != SQLITE_OK)
+ {
+ log_error("Error preparing statement: ", sqlite3_errmsg(db));
+ return nullptr;
+ }
+ Statement statement(stmt);
+ 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++;
+ }
+
+ return statement;
+ }
+
+ void execute(sqlite3* db)
+ {
+ auto statement = this->prepare(db);
+ while (sqlite3_step(statement.get()) != SQLITE_DONE)
+ ;
+ }
+};
+
+template <typename ColumnType>
+void add_param(Query& query, const ColumnType& column)
+{
+ actual_add_param(query, column.value);
+}
+template <>
+void add_param<Id>(Query& query, const Id& column);
+
+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);
+void actual_add_param(Query& query, const OptionalBool& val);
+
+template <typename T>
+typename std::enable_if<!std::is_integral<T>::value, Query&>::type
+operator<<(Query& query, const T&)
+{
+ query.body += T::name;
+ return query;
+}
+
+Query& operator<<(Query& query, const char* str);
+Query& operator<<(Query& query, const std::string& str);
+template <typename Integer>
+typename std::enable_if<std::is_integral<Integer>::value, Query&>::type
+operator<<(Query& query, const Integer& i)
+{
+ query.body += "?";
+ actual_add_param(query, i);
+ return query;
+}
diff --git a/src/database/row.hpp b/src/database/row.hpp
new file mode 100644
index 0000000..e7a58c4
--- /dev/null
+++ b/src/database/row.hpp
@@ -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=0, 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=0, 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(this->columns, db);
+ }
+
+ std::tuple<T...> columns;
+ std::string table_name;
+};
diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp
new file mode 100644
index 0000000..f4d71af
--- /dev/null
+++ b/src/database/select_query.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <database/statement.hpp>
+#include <database/query.hpp>
+#include <logger/logger.hpp>
+#include <database/row.hpp>
+
+#include <utils/optional_bool.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, T>::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 <typename T>
+typename std::enable_if<std::is_same<OptionalBool, T>::value, T>::type
+extract_row_value(Statement& statement, const int i)
+{
+ const auto integer = sqlite3_column_int(statement.get(), i);
+ OptionalBool result;
+ if (integer > 0)
+ result.set_value(true);
+ else if (integer < 0)
+ result.set_value(false);
+ 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();
+ this->body += " from " + this->table_name;
+ }
+
+ template <std::size_t N=0>
+ 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=0>
+ 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);
+ 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;
+};
+
diff --git a/src/database/statement.hpp b/src/database/statement.hpp
new file mode 100644
index 0000000..87cd70f
--- /dev/null
+++ b/src/database/statement.hpp
@@ -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;
+};
diff --git a/src/database/table.cpp b/src/database/table.cpp
new file mode 100644
index 0000000..5929f33
--- /dev/null
+++ b/src/database/table.cpp
@@ -0,0 +1,25 @@
+#include <database/table.hpp>
+
+std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& table_name)
+{
+ std::set<std::string> result;
+ char* errmsg;
+ std::string query{"PRAGMA table_info("s + table_name + ")"};
+ log_debug(query);
+ int res = sqlite3_exec(db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int {
+ constexpr int name_column = 1;
+ std::set<std::string>* result = static_cast<std::set<std::string>*>(param);
+ log_debug("Table has column ", columns[name_column]);
+ if (name_column < columns_nb)
+ result->insert(columns[name_column]);
+ return 0;
+ }, &result, &errmsg);
+
+ if (res != SQLITE_OK)
+ {
+ log_error("Error executing ", query, ": ", errmsg);
+ sqlite3_free(errmsg);
+ }
+
+ return result;
+}
diff --git a/src/database/table.hpp b/src/database/table.hpp
new file mode 100644
index 0000000..411ac6a
--- /dev/null
+++ b/src/database/table.hpp
@@ -0,0 +1,127 @@
+#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>
+#include <set>
+
+using namespace std::string_literals;
+
+std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& table_name);
+
+template <typename ColumnType>
+void add_column_to_table(sqlite3* db, const std::string& table_name)
+{
+ const std::string name = ColumnType::name;
+ std::string query{"ALTER TABLE "s + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType<typename ColumnType::real_type>::type};
+ log_debug(query);
+ char* error;
+ const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error);
+ if (result != SQLITE_OK)
+ {
+ log_error("Error adding column ", name, " to table ", table_name, ": ", error);
+ sqlite3_free(error);
+ }
+}
+
+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 upgrade(sqlite3* db)
+ {
+ const auto existing_columns = get_all_columns_from_table(db, this->name);
+ add_column_if_not_exists(db, existing_columns);
+ }
+
+ void create(sqlite3* db)
+ {
+ std::string res{"CREATE TABLE IF NOT EXISTS "};
+ res += this->name;
+ res += " (\n";
+ this->add_column_create(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=0>
+ typename std::enable_if<N < sizeof...(T), void>::type
+ add_column_if_not_exists(sqlite3* db, const std::set<std::string>& existing_columns)
+ {
+ using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnTypes>()))>::type;
+ if (existing_columns.count(ColumnType::name) != 1)
+ {
+ add_column_to_table<ColumnType>(db, this->name);
+ }
+ add_column_if_not_exists<N+1>(db, existing_columns);
+ }
+ template <std::size_t N=0>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ add_column_if_not_exists(sqlite3*, const std::set<std::string>&)
+ {}
+
+ template <std::size_t N=0>
+ 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=0>
+ typename std::enable_if<N == sizeof...(T), void>::type
+ add_column_create(std::string&)
+ { }
+
+ const std::string name;
+};
diff --git a/src/database/type_to_sql.cpp b/src/database/type_to_sql.cpp
new file mode 100644
index 0000000..bcd9daa
--- /dev/null
+++ b/src/database/type_to_sql.cpp
@@ -0,0 +1,9 @@
+#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";
+template <> const std::string TypeToSQLType<OptionalBool>::type = "INTEGER"; \ No newline at end of file
diff --git a/src/database/type_to_sql.hpp b/src/database/type_to_sql.hpp
new file mode 100644
index 0000000..ba806ab
--- /dev/null
+++ b/src/database/type_to_sql.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <utils/optional_bool.hpp>
+
+#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;
+template <> const std::string TypeToSQLType<OptionalBool>::type; \ No newline at end of file
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index 1d74098..bacb89e 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -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;
}
diff --git a/src/main.cpp b/src/main.cpp
index 1a9b065..5725584 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -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
diff --git a/src/utils/optional_bool.hpp b/src/utils/optional_bool.hpp
new file mode 100644
index 0000000..59bbbab
--- /dev/null
+++ b/src/utils/optional_bool.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <string>
+
+struct OptionalBool
+{
+ OptionalBool() = default;
+
+ OptionalBool(bool value):
+ is_set(true), value(value) {}
+
+ void set_value(bool value)
+ {
+ this->is_set = true;
+ this->value = value;
+ }
+
+ void unset()
+ {
+ this->is_set = false;
+ }
+
+ std::string to_string()
+ {
+ if (this->is_set == false)
+ return "unset";
+ else if (this->value)
+ return "true";
+ else
+ return "false";
+ }
+
+ bool is_set{false};
+ bool value{false};
+};
diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp
index 348c5b5..fdca9bc 100644
--- a/src/utils/reload.cpp
+++ b/src/utils/reload.cpp
@@ -26,7 +26,7 @@ void reload_process()
#ifdef USE_DATABASE
try {
open_database();
- } catch (const litesql::DatabaseError&) {
+ } catch (...) {
log_warning("Re-using the previous database.");
}
#endif
diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp
index ab28cfd..ad4faf8 100644
--- a/src/xmpp/biboumi_adhoc_commands.cpp
+++ b/src/xmpp/biboumi_adhoc_commands.cpp
@@ -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");
@@ -436,15 +434,35 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester,
XmlSubNode instructions(x, "instructions");
instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local());
+ XmlSubNode record_history(x, "field");
+ record_history["var"] = "record_history";
+ record_history["type"] = "list-single";
+ record_history["label"] = "Record history for this channel";
+ record_history["desc"] = "If unset, the value is the one configured globally";
+
+ {
+ // Value selected by default
+ XmlSubNode value(record_history, "value");
+ value.set_inner(options.col<Database::RecordHistoryOptional>().to_string());
+ }
+ // All three possible values
+ for (const auto& val: {"unset", "true", "false"})
+ {
+ XmlSubNode option(record_history, "option");
+ option["label"] = val;
+ XmlSubNode value(option, "value");
+ value.set_inner(val);
+ }
+
XmlSubNode encoding_out(x, "field");
encoding_out["var"] = "encoding_out";
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 +470,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,19 +484,19 @@ 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");
}
}
-void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
+void ConfigureIrcChannelStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)
{
const Jid owner(session.get_owner_jid());
const Jid target(session.get_target_jid());
- if (handle_irc_channel_configuration_form(command_node, owner, target))
+ if (handle_irc_channel_configuration_form(xmpp_component, command_node, owner, target))
{
command_node.delete_all_children();
XmlSubNode note(command_node, "note");
@@ -494,7 +512,7 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co
}
}
-bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target)
+bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const XmlNode& node, const Jid& requester, const Jid& target)
{
const XmlNode* x = node.get_child("x", "jabber:x:data");
if (x)
@@ -502,7 +520,7 @@ bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& reque
if (x->get_tag("type") == "submit")
{
const Iid iid(target.local, {});
- auto options = Database::get_irc_channel_options(requester.local + "@" + requester.domain,
+ auto options = Database::get_irc_channel_options(requester.bare(),
iid.get_server(), iid.get_local());
for (const XmlNode *field: x->get_children("field", "jabber:x:data"))
{
@@ -510,18 +528,43 @@ 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());
+ else if (field->get_tag("var") == "record_history" &&
+ value && !value->get_inner().empty())
+ {
+ OptionalBool& database_value = options.col<Database::RecordHistoryOptional>();
+ if (value->get_inner() == "true")
+ database_value.set_value(true);
+ else if (value->get_inner() == "false")
+ database_value.set_value(false);
+ else
+ database_value.unset();
+ auto& biboumi_component = dynamic_cast<BiboumiComponent&>(xmpp_component);
+ Bridge* bridge = biboumi_component.find_user_bridge(requester.bare());
+ if (bridge)
+ {
+ if (database_value.is_set)
+ bridge->set_record_history(database_value.value);
+ else
+ { // It is unset, we need to fetch the Global option, to
+ // know if it’s enabled or not
+ auto g_options = Database::get_global_options(requester.bare());
+ bridge->set_record_history(g_options.col<Database::RecordHistory>());
+ }
+ }
+ }
+
}
- options.update();
+ options.save(Database::db);
}
return true;
}
diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp
index 7d29cc2..cb6acb9 100644
--- a/src/xmpp/biboumi_adhoc_commands.hpp
+++ b/src/xmpp/biboumi_adhoc_commands.hpp
@@ -20,7 +20,7 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, const Jid& target);
void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
-bool handle_irc_channel_configuration_form(const XmlNode& node, const Jid& requester, const Jid& target);
+bool handle_irc_channel_configuration_form(XmppComponent&, const XmlNode& node, const Jid& requester, const Jid& target);
void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node);
void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node);
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index ca3a887..32f3968 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -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);
}
@@ -721,7 +719,7 @@ bool BiboumiComponent::handle_room_configuration_form(const XmlNode& query, cons
return false;
Jid requester(from);
- if (!handle_irc_channel_configuration_form(query, requester, to))
+ if (!handle_irc_channel_configuration_form(*this, query, requester, to))
return false;
Stanza iq("iq");
diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp
index ac9bde4..87311f9 100644
--- a/src/xmpp/biboumi_component.hpp
+++ b/src/xmpp/biboumi_component.hpp
@@ -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);
diff --git a/tests/database.cpp b/tests/database.cpp
index 4e2be14..f49220a 100644
--- a/tests/database.cpp
+++ b/tests/database.cpp
@@ -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,20 @@ 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";
+ CHECK(o.col<Database::RecordHistoryOptional>().is_set == false);
+ o.col<Database::RecordHistoryOptional>().set_value(false);
+ 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");
+ CHECK(o.col<Database::RecordHistoryOptional>().is_set == true);
+ CHECK(o.col<Database::RecordHistoryOptional>().value == false);
}
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 +53,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");
}
}
}
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py
index f9e04a8..19dc270 100644
--- a/tests/end_to_end/__main__.py
+++ b/tests/end_to_end/__main__.py
@@ -278,7 +278,6 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None):
def save_current_timestamp_plus_delta(key, delta, message, xmpp):
now_plus_delta = datetime.datetime.utcnow() + delta
xmpp.saved_values[key] = now_plus_delta.strftime("%FT%T.967Z")
- print(xmpp.saved_values[key])
def sleep_for(duration, xmpp, biboumi):
time.sleep(duration)
@@ -2417,7 +2416,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"))
@@ -2467,6 +2466,7 @@ if __name__ == '__main__':
partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
"/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']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='unset']",
),
after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))
),
@@ -2476,6 +2476,7 @@ if __name__ == '__main__':
"<field var='ports' />"
"<field var='encoding_out'><value>UTF-8</value></field>"
"<field var='encoding_in'><value>latin-1</value></field>"
+ "<field var='record_history'><value>true</value></field>"
"</x></command></iq>"),
partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
@@ -2484,6 +2485,7 @@ if __name__ == '__main__':
"/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC channel #foo on server irc.localhost']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_in']/dataform:value[text()='latin-1']",
"/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='encoding_out']/dataform:value[text()='UTF-8']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='list-single'][@var='record_history']/dataform:value[text()='true']",
"/iq/commands:command/commands:actions/commands:next",
),
after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))
diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf
index cdb06d5..ccfbd90 100644
--- a/tests/end_to_end/ircd.conf
+++ b/tests/end_to_end/ircd.conf
@@ -498,7 +498,7 @@ general {
reject_after_count = 3;
reject_duration = 5 minutes;
throttle_duration = 60;
- throttle_count = 4;
+ throttle_count = 8888;
max_ratelimit_tokens = 30;
away_interval = 30;
certfp_method = sha1;