add archive_expires_after option
This commit is contained in:
committed by
mathieui
parent
4de88b5160
commit
6ed185a8a5
@@ -22,6 +22,8 @@ For admins
|
||||
- Options default_persistence_allowed, persist_user_denylist, and
|
||||
persist_user_allowlist have been added to granualy control who can set the
|
||||
persist option
|
||||
- Option archive_expires_after has been added to delete older messages from the
|
||||
database
|
||||
|
||||
For packagers
|
||||
-------------
|
||||
|
||||
@@ -176,6 +176,10 @@ file(GLOB source_bridge
|
||||
src/bridge/*.[hc]pp)
|
||||
add_library(bridge OBJECT ${source_bridge})
|
||||
|
||||
file(GLOB source_scheduler
|
||||
src/scheduler/*.[hc]pp)
|
||||
add_library(scheduler OBJECT ${source_scheduler})
|
||||
|
||||
file(GLOB source_config
|
||||
src/config/*.[hc]pp)
|
||||
add_library(config OBJECT ${source_config})
|
||||
@@ -217,6 +221,7 @@ add_executable(${PROJECT_NAME} src/main.cpp
|
||||
$<TARGET_OBJECTS:network>
|
||||
$<TARGET_OBJECTS:xmpp>
|
||||
$<TARGET_OBJECTS:bridge>
|
||||
$<TARGET_OBJECTS:scheduler>
|
||||
$<TARGET_OBJECTS:irc>
|
||||
$<TARGET_OBJECTS:identd>)
|
||||
|
||||
@@ -230,6 +235,7 @@ add_executable(test_suite ${source_tests}
|
||||
$<TARGET_OBJECTS:network>
|
||||
$<TARGET_OBJECTS:xmpp>
|
||||
$<TARGET_OBJECTS:bridge>
|
||||
$<TARGET_OBJECTS:scheduler>
|
||||
$<TARGET_OBJECTS:irc>
|
||||
$<TARGET_OBJECTS:identd>)
|
||||
set_target_properties(test_suite PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
@@ -266,6 +266,28 @@ default_persistence_allowed
|
||||
If a given XMPP user or domain is not in either denylist or allowlist this
|
||||
option decides if the user is allowed to persist or not.
|
||||
|
||||
archive_expires_after
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Messages in the archive can expire after some time, by default never. This
|
||||
can be changed by this setting
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
# one day
|
||||
archive_expires_after=1d
|
||||
# one week
|
||||
archive_expires_after=1w
|
||||
# two months
|
||||
archive_expires_after=2m
|
||||
# one year
|
||||
archive_expires_after=1y
|
||||
# 1h
|
||||
archive_expires_after=1h
|
||||
# keep messages forever, the default
|
||||
archive_expires_after=0
|
||||
|
||||
|
||||
TLS configuration
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifdef USE_DATABASE
|
||||
|
||||
#include <database/select_query.hpp>
|
||||
#include <database/delete_query.hpp>
|
||||
#include <database/save.hpp>
|
||||
#include <database/database.hpp>
|
||||
#include <utils/get_first_non_empty.hpp>
|
||||
@@ -317,6 +318,14 @@ std::vector<Database::RosterItem> Database::get_full_roster()
|
||||
return query.execute(*Database::db);
|
||||
}
|
||||
|
||||
void Database::delete_old_muclines(const long& seconds)
|
||||
{
|
||||
auto query = DeleteQuery(Database::muc_log_lines.get_name());
|
||||
query << " WHERE " << Database::Date{} << "<" << seconds;
|
||||
|
||||
query.execute(*Database::db);
|
||||
}
|
||||
|
||||
void Database::close()
|
||||
{
|
||||
Database::db = nullptr;
|
||||
|
||||
@@ -153,6 +153,7 @@ class Database
|
||||
static void delete_roster_item(const std::string& local, const std::string& remote);
|
||||
static std::vector<Database::RosterItem> get_contact_list(const std::string& local);
|
||||
static std::vector<Database::RosterItem> get_full_roster();
|
||||
static void delete_old_muclines(const long& seconds);
|
||||
|
||||
static void close();
|
||||
static void open(const std::string& filename);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <csignal>
|
||||
|
||||
#include <identd/identd_server.hpp>
|
||||
#include <scheduler/scheduler.hpp>
|
||||
|
||||
// A flag set by the SIGINT signal handler.
|
||||
static std::atomic<bool> stop(false);
|
||||
@@ -107,6 +108,7 @@ static int main_loop(std::string hostname, std::string password)
|
||||
if (Config::get_int("identd_port", 113) != 0)
|
||||
identd = std::make_unique<IdentdServer>(*xmpp_component, p, static_cast<uint16_t>(Config::get_int("identd_port", 113)));
|
||||
|
||||
Scheduler::start_scheduler();
|
||||
auto timeout = TimedEventsManager::instance().get_timeout();
|
||||
while (p->poll(timeout) != -1)
|
||||
{
|
||||
@@ -130,6 +132,7 @@ static int main_loop(std::string hostname, std::string password)
|
||||
#endif
|
||||
if (identd)
|
||||
identd->shutdown();
|
||||
Scheduler::stop_scheduler();
|
||||
// Cancel the timer for a potential reconnection
|
||||
TimedEventsManager::instance().cancel("XMPP reconnection");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "biboumi.h"
|
||||
#include <config/config.hpp>
|
||||
#include <scheduler/scheduler.hpp>
|
||||
#include <utils/parse_duration.hpp>
|
||||
#include <utils/timed_events.hpp>
|
||||
|
||||
#ifdef USE_DATABASE
|
||||
#include <database/database.hpp>
|
||||
#endif
|
||||
|
||||
static const std::unordered_map<
|
||||
std::string, std::pair<std::chrono::seconds, std::function<void()>>>
|
||||
events = {
|
||||
#ifdef USE_DATABASE
|
||||
{"cleanup_old_messages",
|
||||
{60s * 10 /* 10min */, &Scheduler::cleanup_old_messages}},
|
||||
#endif
|
||||
};
|
||||
|
||||
void Scheduler::start_scheduler() {
|
||||
for (const auto &job : events) {
|
||||
const std::string name = job.first;
|
||||
const std::chrono::seconds interval = job.second.first;
|
||||
const std::function<void()> job_function = job.second.second;
|
||||
// Run it once at startup
|
||||
job_function();
|
||||
// Then every interval
|
||||
TimedEventsManager::instance().add_event(
|
||||
TimedEvent(interval, job_function, name));
|
||||
}
|
||||
}
|
||||
|
||||
void Scheduler::stop_scheduler() {
|
||||
for (const auto &job : events) {
|
||||
const std::string name = job.first;
|
||||
TimedEventsManager::instance().cancel(name);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_DATABASE
|
||||
void Scheduler::cleanup_old_messages() {
|
||||
// Get the config value
|
||||
const std::string archive_expires_after =
|
||||
Config::get("archive_expires_after", "0");
|
||||
const std::optional<long> seconds =
|
||||
utils::parse_duration(archive_expires_after);
|
||||
// If it's actually set and relevant, start cleanup
|
||||
if (seconds.has_value() && seconds.value() != 0) {
|
||||
// Calculate age of messages to be deleted
|
||||
log_debug("[cleanup_old_messages] Deleting messages older than ",
|
||||
archive_expires_after, " (", seconds.value(), " seconds)");
|
||||
|
||||
// Convert the config value to a duration
|
||||
const auto duration = std::chrono::duration<long>(seconds.value());
|
||||
// Get timestamp now relative to unix epoch
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
// Older than this get deleted
|
||||
const auto ago = now - duration;
|
||||
// Convert to seconds since epoch
|
||||
const auto since_epoch = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
ago.time_since_epoch());
|
||||
Database::delete_old_muclines(since_epoch.count());
|
||||
} else
|
||||
log_debug("[cleanup_old_messages] Not deleting anything. "
|
||||
"archive_expires_after is ",
|
||||
archive_expires_after);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
static void start_scheduler();
|
||||
static void stop_scheduler();
|
||||
|
||||
// Jobs
|
||||
static void cleanup_old_messages();
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utils/parse_duration.hpp>
|
||||
|
||||
namespace utils {
|
||||
|
||||
// Strongly inspired from prosody
|
||||
// https://hg.prosody.im/trunk/file/0ff11f2e87cd/util/human/io.lua#l214
|
||||
static const long day = 86400;
|
||||
|
||||
static const std::unordered_map<std::string, long> multipliers = {
|
||||
{"d", day}, {"w", day * 7}, {"mon", day * 31}, {"y", day * 365.2425},
|
||||
{"s", 1}, {"min", 60}, {"h", 3600}, {"ho", 3600},
|
||||
};
|
||||
|
||||
std::optional<long> parse_duration(const std::string &duration) {
|
||||
std::size_t ptr;
|
||||
long number;
|
||||
try {
|
||||
number = std::stol(duration, &ptr, 10);
|
||||
} catch (std::invalid_argument &) {
|
||||
return {};
|
||||
}
|
||||
const std::string m = duration.substr(ptr);
|
||||
|
||||
if (!multipliers.contains(m))
|
||||
return {};
|
||||
|
||||
const long multiplier = multipliers.at(m);
|
||||
|
||||
return multiplier * number;
|
||||
}
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace utils {
|
||||
std::optional<long> parse_duration(const std::string &duration);
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "catch2/catch_test_macros.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <utils/parse_duration.hpp>
|
||||
|
||||
TEST_CASE("parse_duration - 2d")
|
||||
{
|
||||
const long expected = (60 * 60 * 24) * 2;
|
||||
const std::optional<long> output = utils::parse_duration("2d");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 5w")
|
||||
{
|
||||
const long expected = ((60 * 60 * 24) * 7) * 5;
|
||||
const std::optional<long> output = utils::parse_duration("5w");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - asdf")
|
||||
{
|
||||
const std::optional<long> expected;
|
||||
const std::optional<long> output = utils::parse_duration("asdf");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 1s")
|
||||
{
|
||||
const long expected = 1;
|
||||
const std::optional<long> output = utils::parse_duration("1s");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 2mon")
|
||||
{
|
||||
const long expected = ((60 * 60 * 24) * 31) * 2;
|
||||
const std::optional<long> output = utils::parse_duration("2mon");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 5y")
|
||||
{
|
||||
const long expected = (long)((60 * 60 * 24) * 365.2425) * 5;
|
||||
const std::optional<long> output = utils::parse_duration("5y");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 5")
|
||||
{
|
||||
const std::optional<long> expected;
|
||||
const std::optional<long> output = utils::parse_duration("5");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - <empty>")
|
||||
{
|
||||
const std::optional<long> expected;
|
||||
const std::optional<long> output = utils::parse_duration("");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 5blorbs")
|
||||
{
|
||||
const std::optional<long> expected;
|
||||
const std::optional<long> output = utils::parse_duration("5blorbs");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
|
||||
TEST_CASE("parse_duration - 5y2d")
|
||||
{
|
||||
const std::optional<long> expected;
|
||||
const std::optional<long> output = utils::parse_duration("5y2d");
|
||||
|
||||
CHECK(expected == output);
|
||||
}
|
||||
Reference in New Issue
Block a user