[archive] remove old unpacked archives and a bunch of other stuff

pull/835/head
Timothy Stack 3 years ago
parent ae422bb37e
commit 56bee6f4c9

@ -5,7 +5,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

@ -354,12 +354,14 @@ add_library(diag STATIC
all_logs_vtab.hh
archive_manager.hh
archive_manager.cfg.hh
attr_line.hh
auto_fd.hh
auto_mem.hh
auto_pid.hh
big_array.hh
bottom_status_source.hh
bound_tags.hh
byte_array.hh
command_executor.hh
column_namer.hh
@ -373,7 +375,6 @@ add_library(diag STATIC
field_overlay_source.hh
file_collection.hh
file_format.hh
file_vtab.hh
files_sub_source.hh
filter_observer.hh
filter_status_source.hh
@ -387,6 +388,8 @@ add_library(diag STATIC
hotkeys.hh
base/humanize.hh
input_dispatcher.hh
base/injector.hh
base/injector.bind.hh
base/intern_string.hh
base/is_utf8.hh
k_merge_tree.h

@ -249,6 +249,7 @@ noinst_HEADERS = \
all_logs_vtab.hh \
ansi_scrubber.hh \
archive_manager.hh \
archive_manager.cfg.hh \
attr_line.hh \
auto_fd.hh \
auto_mem.hh \
@ -257,6 +258,7 @@ noinst_HEADERS = \
bin2c.hh \
bookmarks.hh \
bottom_status_source.hh \
bound_tags.hh \
byte_array.hh \
column_namer.hh \
command_executor.hh \
@ -272,7 +274,6 @@ noinst_HEADERS = \
field_overlay_source.hh \
file_collection.hh \
file_format.hh \
file_vtab.hh \
files_sub_source.hh \
filter_observer.hh \
filter_status_source.hh \
@ -413,7 +414,6 @@ libdiag_a_SOURCES = \
field_overlay_source.cc \
file_collection.cc \
file_format.cc \
file_vtab.cc \
files_sub_source.cc \
filter_observer.cc \
filter_status_source.cc \
@ -490,11 +490,14 @@ libdiag_a_SOURCES = \
xterm_mouse.cc \
spookyhash/SpookyV2.cpp
PLUGIN_SRCS = \
file_vtab.cc
lnav.$(OBJEXT): help-txt.h init-sql.h
lnav_SOURCES = lnav.cc
lnav_SOURCES = lnav.cc $(PLUGIN_SRCS)
lnav_test_SOURCES = lnav.cc test_override.c
lnav_test_SOURCES = lnav.cc test_override.c $(PLUGIN_SRCS)
bin2c$(BUILD_EXEEXT): bin2c.c
$(AM_V_CC) $(CC_FOR_BUILD) $(CPPFLAGS) $(LDFLAGS) -g3 -o $@ $? -lz

@ -42,10 +42,12 @@
#include "auto_fd.hh"
#include "auto_mem.hh"
#include "fmt/format.h"
#include "base/injector.hh"
#include "base/lnav_log.hh"
#include "lnav_util.hh"
#include "archive_manager.hh"
#include "archive_manager.cfg.hh"
namespace fs = ghc::filesystem;
@ -78,7 +80,7 @@ public:
lockf(this->lh_fd, F_ULOCK, 0);
};
explicit archive_lock(const ghc::filesystem::path& archive_path) {
explicit archive_lock(const fs::path& archive_path) {
auto lock_path = archive_path;
lock_path += ".lck";
@ -89,7 +91,7 @@ public:
auto_fd lh_fd;
};
bool is_archive(const ghc::filesystem::path& filename)
bool is_archive(const fs::path& filename)
{
#if HAVE_ARCHIVE_H
auto_mem<archive> arc(archive_read_free);
@ -139,13 +141,20 @@ bool is_archive(const ghc::filesystem::path& filename)
return false;
}
static
fs::path archive_cache_path()
{
auto subdir_name = fmt::format("lnav-{}-archives", getuid());
auto tmp_path = fs::temp_directory_path();
return tmp_path / fs::path(subdir_name);
}
fs::path
filename_to_tmp_path(const std::string &filename)
{
auto fn_path = fs::path(filename);
auto basename = fn_path.filename().string();
auto subdir_name = fmt::format("lnav-{}-archives", getuid());
auto tmp_path = fs::temp_directory_path();
hasher h;
h.update(basename);
@ -161,7 +170,7 @@ filename_to_tmp_path(const std::string &filename)
}
basename = fmt::format("arc-{}-{}", h.to_string(), basename);
return tmp_path / fs::path(subdir_name) / basename;
return archive_cache_path() / basename;
}
#if HAVE_ARCHIVE_H
@ -170,7 +179,7 @@ copy_data(const std::string& filename,
struct archive *ar,
struct archive_entry *entry,
struct archive *aw,
const ghc::filesystem::path &entry_path,
const fs::path &entry_path,
struct extract_progress *ep)
{
int r;
@ -200,7 +209,7 @@ copy_data(const std::string& filename,
ep->ep_out_size.fetch_add(size);
if ((total - last_space_check) > (1024 * 1024)) {
auto tmp_space = ghc::filesystem::space(entry_path);
auto tmp_space = fs::space(entry_path);
if (tmp_space.available < MIN_FREE_SPACE) {
return Err(fmt::format(
@ -226,8 +235,8 @@ static walk_result_t extract(const std::string &filename, const extract_cb &cb)
done_path += ".done";
if (ghc::filesystem::exists(done_path)) {
ghc::filesystem::last_write_time(
if (fs::exists(done_path)) {
fs::last_write_time(
done_path, std::chrono::system_clock::now());
log_debug("already extracted! %s", done_path.c_str());
return Ok();
@ -319,7 +328,11 @@ walk_result_t walk_archive_files(
#if HAVE_ARCHIVE_H
auto tmp_path = filename_to_tmp_path(filename);
TRY(extract(filename, cb));
auto result = extract(filename, cb);
if (result.isErr()) {
fs::remove_all(tmp_path);
return result;
}
for (const auto& entry : fs::recursive_directory_iterator(tmp_path)) {
if (!entry.is_regular_file()) {
@ -335,4 +348,40 @@ walk_result_t walk_archive_files(
#endif
}
void cleanup_cache()
{
(void) std::async(std::launch::async, []() {
auto now = std::chrono::system_clock::now();
auto cache_path = archive_cache_path();
auto cfg = injector::get<const config&>();
std::vector<fs::path> to_remove;
log_debug("cache-ttl %d", cfg.amc_cache_ttl.count());
for (const auto& entry : fs::directory_iterator(cache_path)) {
if (entry.path().extension() != ".done") {
continue;
}
auto mtime = fs::last_write_time(entry.path());
auto exp_time = mtime + cfg.amc_cache_ttl;
if (now < exp_time) {
continue;
}
to_remove.emplace_back(entry.path());
}
for (auto& entry : to_remove) {
log_debug("removing cached archive: %s", entry.c_str());
fs::remove(entry);
entry.replace_extension(".lck");
fs::remove(entry);
entry.replace_extension();
fs::remove_all(entry);
}
});
}
}

@ -0,0 +1,45 @@
/**
* Copyright (c) 2021, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file archive_manager.cfg.hh
*/
#ifndef lnav_archive_manager_cfg_hh
#define lnav_archive_manager_cfg_hh
#include <chrono>
namespace archive_manager {
struct config {
std::chrono::seconds amc_cache_ttl;
};
}
#endif

@ -68,6 +68,9 @@ walk_result_t walk_archive_files(
const std::function<void(
const ghc::filesystem::path &,
const ghc::filesystem::directory_entry &)> &);
void cleanup_cache();
}
#endif

@ -13,6 +13,8 @@ noinst_HEADERS = \
func_util.hh \
future_util.hh \
humanize.hh \
injector.hh \
injector.bind.hh \
intern_string.hh \
is_utf8.hh \
lnav_log.hh \

@ -0,0 +1,97 @@
/**
* Copyright (c) 2021, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file injector.bind.hh
*/
#ifndef lnav_injector_bind_hh
#define lnav_injector_bind_hh
#include "injector.hh"
namespace injector {
template<typename T, typename...Annotations>
struct bind : singleton_storage<T, Annotations...> {
static bool to_singleton() noexcept {
static T storage;
singleton_storage<T, Annotations...>::ss_data = &storage;
return true;
}
template<typename...Args>
static bool to_instance(T* (*f)(Args...)) noexcept {
singleton_storage<T, Annotations...>::ss_data = f(::injector::get<Args>()...);
return true;
}
template<typename I>
static bool to(Impl<I> i) {
singleton_storage<T, Annotations...>::ss_factory = []() {
return std::make_shared<I>();
};
return true;
}
};
template<typename T>
struct bind_multiple : multiple_storage<T> {
bind_multiple() noexcept = default;
template<typename I, typename R, typename ...Args>
bind_multiple& add(R (*)(Args...)) {
multiple_storage<T>::ms_factories[typeid(I).name()] = []() {
return std::make_shared<I>(::injector::get<Args>()...);
};
return *this;
}
template<typename I,
std::enable_if_t<has_injectable<I>::value, bool> = true>
bind_multiple& add() {
typename I::injectable *i = nullptr;
return this->add<I>(i);
}
template<typename I,
std::enable_if_t<!has_injectable<I>::value, bool> = true>
bind_multiple& add() noexcept {
multiple_storage<T>::ms_factories[typeid(I).name()] = []() {
return std::make_shared<I>();
};
return *this;
}
};
}
#endif

@ -0,0 +1,146 @@
/**
* Copyright (c) 2021, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file injector.hh
*/
#ifndef lnav_injector_hh
#define lnav_injector_hh
#include <assert.h>
#include <map>
#include <memory>
#include <vector>
#include <type_traits>
#include "base/lnav_log.hh"
namespace injector {
template<class ...>
using void_t = void;
template <class, class = void>
struct has_injectable : std::false_type {};
template <class T>
struct has_injectable<T, void_t<typename T::injectable>> : std::true_type
{};
template<typename T, typename...Annotations>
struct singleton_storage {
static T *get() {
assert(ss_data != nullptr);
return ss_data;
}
static std::shared_ptr<T> create() {
return ss_factory();
}
protected:
static T *ss_data;
static std::function<std::shared_ptr<T>()> ss_factory;
};
template<typename T, typename...Annotations>
T *singleton_storage<T, Annotations...>::ss_data = nullptr;
template<typename T, typename...Annotations>
std::function<std::shared_ptr<T>()> singleton_storage<T, Annotations...>::ss_factory;
template<typename T>
struct Impl {
using type = T;
};
template<typename T>
struct multiple_storage {
static std::vector<std::shared_ptr<T>> create() {
std::vector<std::shared_ptr<T>> retval;
for (const auto& pair : ms_factories) {
retval.template emplace_back(pair.second());
}
return retval;
}
protected:
static std::map<std::string, std::function<std::shared_ptr<T>()>> ms_factories;
};
template<typename T>
std::map<std::string, std::function<std::shared_ptr<T>()>>
multiple_storage<T>::ms_factories;
template<typename T, typename...Annotations,
std::enable_if_t<std::is_reference<T>::value, bool> = true>
T get()
{
using plain_t = std::remove_const_t<std::remove_reference_t<T>>;
return *singleton_storage<plain_t, Annotations...>::get();
}
template<typename T, typename...Annotations,
std::enable_if_t<std::is_pointer<T>::value, bool> = true>
T get()
{
using plain_t = std::remove_const_t<std::remove_pointer_t<T>>;
return singleton_storage<plain_t, Annotations...>::get();
}
template<class T>
struct is_shared_ptr : std::false_type {};
template<class T>
struct is_shared_ptr<std::shared_ptr<T>> : std::true_type {};
template<class T>
struct is_vector : std::false_type {};
template<class T>
struct is_vector<std::vector<T>> : std::true_type {};
template<typename T,
std::enable_if_t<is_shared_ptr<T>::value, bool> = true>
T get()
{
return singleton_storage<typename T::element_type>::create();
}
template<typename T,
std::enable_if_t<is_vector<T>::value, bool> = true>
T get()
{
return multiple_storage<typename T::value_type::element_type>::create();
}
}
#endif

@ -1,5 +1,5 @@
/**
* Copyright (c) 2017, Timothy Stack
* Copyright (c) 2021, Timothy Stack
*
* All rights reserved.
*
@ -25,15 +25,13 @@
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file bound_tags.hh
*/
#ifndef file_vtab_hh
#define file_vtab_hh
#include <sqlite3.h>
#include "file_collection.hh"
#ifndef lnav_bound_tags_hh
#define lnav_bound_tags_hh
int register_file_vtab(sqlite3 *db, file_collection &fc);
struct last_relative_time_tag {};
#endif

@ -474,8 +474,7 @@ static Result<string, string> execute_file_contents(exec_context &ec, const ghc:
Result<string, string> execute_file(exec_context &ec, const string &path_and_args, bool multiline)
{
map<string, vector<script_metadata> > scripts;
map<string, vector<script_metadata> >::iterator iter;
available_scripts scripts;
vector<string> split_args;
string retval, msg;
shlex lexer(path_and_args);
@ -518,11 +517,12 @@ Result<string, string> execute_file(exec_context &ec, const string &path_and_arg
map<string, const char *>::iterator internal_iter;
find_format_scripts(lnav_data.ld_config_paths, scripts);
if ((iter = scripts.find(script_name)) != scripts.end()) {
auto iter = scripts.as_scripts.find(script_name);
if (iter != scripts.as_scripts.end()) {
paths_to_exec = iter->second;
}
if (script_name == "-" || script_name == "/dev/stdin") {
paths_to_exec.push_back({script_name});
paths_to_exec.push_back({script_name, "", "", ""});
} else if (access(script_name.c_str(), R_OK) == 0) {
struct script_metadata meta;

@ -32,9 +32,10 @@
#include <unistd.h>
#include <string.h>
#include "base/injector.bind.hh"
#include "base/lnav_log.hh"
#include "file_collection.hh"
#include "logfile.hh"
#include "file_vtab.hh"
#include "session_data.hh"
#include "vtab_module.hh"
#include "log_format.hh"
@ -44,6 +45,7 @@ using namespace std;
struct lnav_file : public tvt_iterator_cursor<lnav_file> {
using iterator = vector<shared_ptr<logfile>>::iterator;
static constexpr const char *NAME = "lnav_file";
static constexpr const char *CREATE_STMT = R"(
-- Access lnav's open file list through this table.
CREATE TABLE lnav_file (
@ -56,7 +58,7 @@ CREATE TABLE lnav_file (
);
)";
lnav_file(file_collection& fc) : lf_collection(fc) {
explicit lnav_file(file_collection& fc) : lf_collection(fc) {
}
iterator begin() {
@ -162,15 +164,10 @@ CREATE TABLE lnav_file (
file_collection &lf_collection;
};
struct injectable_lnav_file : vtab_module<lnav_file> {
using vtab_module<lnav_file>::vtab_module;
using injectable = injectable_lnav_file(file_collection&);
};
int register_file_vtab(sqlite3 *db, file_collection &fc)
{
static auto mod = std::make_shared<vtab_module<lnav_file>>(fc);
int rc;
rc = mod->create(db, "lnav_file");
ensure(rc == SQLITE_OK);
return rc;
}
static auto file_binder = injector::bind_multiple<vtab_module_base>()
.add<injectable_lnav_file>();

@ -35,6 +35,7 @@
#include <grp.h>
#include <pwd.h>
#include "base/injector.hh"
#include "base/lnav_log.hh"
#include "sql_util.hh"
#include "vtab_module.hh"
@ -65,6 +66,7 @@ enum {
};
struct fstat_table {
static constexpr const char *NAME = "fstat";
static constexpr const char *CREATE_STMT = R"(
CREATE TABLE fstat (
st_parent TEXT,

@ -29,6 +29,7 @@
#include "config.h"
#include "base/injector.hh"
#include "base/math_util.hh"
#include "lnav.hh"
#include "bookmarks.hh"
@ -47,6 +48,8 @@
#include "shlex.hh"
#include "lnav_util.hh"
#include "lnav_config.hh"
#include "bound_tags.hh"
#include "xterm_mouse.hh"
using namespace std;
@ -235,10 +238,11 @@ bool handle_paging_key(int ch)
case KEY_F(2):
if (xterm_mouse::is_available()) {
lnav_data.ld_mouse.set_enabled(!lnav_data.ld_mouse.is_enabled());
auto mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_enabled(!mouse_i.is_enabled());
lnav_data.ld_rl_view->set_value(
ok_prefix("info: mouse mode -- ") +
(lnav_data.ld_mouse.is_enabled() ?
(mouse_i.is_enabled() ?
ANSI_BOLD("enabled") : ANSI_BOLD("disabled")));
}
else {
@ -893,13 +897,16 @@ bool handle_paging_key(int ch)
case 'r':
case 'R':
if (lss) {
if (lnav_data.ld_last_relative_time.empty()) {
auto &last_time =
injector::get<const relative_time&, last_relative_time_tag>();
if (last_time.empty()) {
lnav_data.ld_rl_view->set_value(
"Use the 'goto' command to set the relative time to move by");
}
else {
vis_line_t vl = tc->get_top(), new_vl;
relative_time rt = lnav_data.ld_last_relative_time;
relative_time rt = last_time;
content_line_t cl;
struct exttm tm;
bool done = false;

@ -7,6 +7,27 @@
"description": "Specifies the type of this file",
"type": "string"
},
"tuning": {
"description": "Internal settings",
"title": "/tuning",
"type": "object",
"properties": {
"archive-manager": {
"description": "Settings related to opening archive files",
"title": "/tuning/archive-manager",
"type": "object",
"properties": {
"cache-ttl": {
"title": "/tuning/archive-manager/cache-ttl",
"description": "The time-to-live for unpacked archives",
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"ui": {
"description": "User-interface settings",
"title": "/ui",

@ -80,8 +80,10 @@
#include "init-sql.h"
#include "logfile.hh"
#include "base/func_util.hh"
#include "base/injector.bind.hh"
#include "base/string_util.hh"
#include "base/lnav_log.hh"
#include "bound_tags.hh"
#include "lnav_util.hh"
#include "ansi_scrubber.hh"
#include "listview_curses.hh"
@ -115,7 +117,6 @@
#include "environ_vtab.hh"
#include "views_vtab.hh"
#include "all_logs_vtab.hh"
#include "file_vtab.hh"
#include "regexp_vtab.hh"
#include "fstat_vtab.hh"
#include "xpath_vtab.hh"
@ -139,6 +140,7 @@
#include "url_loader.hh"
#include "shlex.hh"
#include "log_actions.hh"
#include "archive_manager.hh"
#ifndef SYSCONFDIR
#define SYSCONFDIR "/usr/etc"
@ -225,6 +227,23 @@ static std::vector<std::string> DEFAULT_DB_KEY_NAMES = {
const static size_t MAX_STDIN_CAPTURE_SIZE = 10 * 1024 * 1024;
static auto bound_active_files =
injector::bind<file_collection>::to_instance(+[]() {
return &lnav_data.ld_active_files;
});
static auto bound_last_rel_time =
injector::bind<relative_time, last_relative_time_tag>::to_singleton();
static auto bound_term_extra =
injector::bind<term_extra>::to_singleton();
static auto bound_xterm_mouse =
injector::bind<xterm_mouse>::to_singleton();
static auto bound_scripts =
injector::bind<available_scripts>::to_singleton();
bool setup_logline_table(exec_context &ec)
{
// Hidden columns don't show up in the table_info pragma.
@ -698,140 +717,6 @@ static void sigchld(int sig)
lnav_data.ld_child_terminated = true;
}
vis_line_t next_cluster(
vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
const vis_line_t top)
{
textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
vis_bookmarks &bm = tc->get_bookmarks();
bookmark_vector<vis_line_t> &bv = bm[bt];
bool top_is_marked = binary_search(bv.begin(), bv.end(), top);
vis_line_t last_top(top), new_top(top), tc_height;
unsigned long tc_width;
int hit_count = 0;
tc->get_dimensions(tc_height, tc_width);
while ((new_top = (bv.*f)(new_top)) != -1) {
int diff = new_top - last_top;
hit_count += 1;
if (!top_is_marked || diff > 1) {
return new_top;
}
else if (hit_count > 1 && std::abs(new_top - top) >= tc_height) {
return vis_line_t(new_top - diff);
}
else if (diff < -1) {
last_top = new_top;
while ((new_top = (bv.*f)(new_top)) != -1) {
if ((std::abs(last_top - new_top) > 1) ||
(hit_count > 1 && (std::abs(top - new_top) >= tc_height))) {
break;
}
last_top = new_top;
}
return last_top;
}
last_top = new_top;
}
if (last_top != top) {
return last_top;
}
return -1_vl;
}
bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top)
{
textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
vis_line_t new_top;
new_top = next_cluster(f, bt, top);
if (new_top == -1) {
new_top = next_cluster(f, bt,
tc->is_selectable() ?
tc->get_selection() :
tc->get_top());
}
if (new_top != -1) {
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
lh->loc_history_append(new_top);
};
if (tc->is_selectable()) {
tc->set_selection(new_top);
} else {
tc->set_top(new_top);
}
return true;
}
alerter::singleton().chime();
return false;
}
void previous_cluster(bookmark_type_t *bt, textview_curses *tc)
{
key_repeat_history &krh = lnav_data.ld_key_repeat_history;
vis_line_t height, new_top, initial_top;
unsigned long width;
if (tc->is_selectable()) {
initial_top = tc->get_selection();
} else {
initial_top = tc->get_top();
}
new_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
bt,
initial_top);
tc->get_dimensions(height, width);
if (krh.krh_count > 1 &&
initial_top < (krh.krh_start_line - (1.5 * height)) &&
(initial_top - new_top) < height) {
bookmark_vector<vis_line_t> &bv = tc->get_bookmarks()[bt];
new_top = bv.next(std::max(0_vl, initial_top - height));
}
if (new_top != -1) {
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
lh->loc_history_append(new_top);
};
if (tc->is_selectable()) {
tc->set_selection(new_top);
} else {
tc->set_top(new_top);
}
}
else {
alerter::singleton().chime();
}
}
vis_line_t search_forward_from(textview_curses *tc)
{
vis_line_t height, retval =
tc->is_selectable() ? tc->get_selection() : tc->get_top();
key_repeat_history &krh = lnav_data.ld_key_repeat_history;
unsigned long width;
tc->get_dimensions(height, width);
if (krh.krh_count > 1 &&
retval > (krh.krh_start_line + (1.5 * height))) {
retval += vis_line_t(0.90 * height);
}
return retval;
}
static void handle_rl_key(int ch)
{
switch (ch) {
@ -1310,8 +1195,10 @@ static void looper()
ui_periodic_timer::singleton();
lnav_data.ld_mouse.set_behavior(&lb);
lnav_data.ld_mouse.set_enabled(check_experimental("mouse"));
auto mouse_i = injector::get<xterm_mouse&>();
mouse_i.set_behavior(&lb);
mouse_i.set_enabled(check_experimental("mouse"));
lnav_data.ld_window = sc.get_window();
keypad(stdscr, TRUE);
@ -1434,7 +1321,7 @@ static void looper()
sb.push_back(bind_mem(&bottom_status_source::update_line_number, &lnav_data.ld_bottom_source));
sb.push_back(bind_mem(&bottom_status_source::update_percent, &lnav_data.ld_bottom_source));
sb.push_back(bind_mem(&bottom_status_source::update_marks, &lnav_data.ld_bottom_source));
sb.push_back(bind_mem(&term_extra::update_title, &lnav_data.ld_term_extra));
sb.push_back(bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
lnav_data.ld_match_view.set_show_bottom_border(true);
@ -1454,7 +1341,7 @@ static void looper()
id.id_escape_matcher = match_escape_seq;
id.id_escape_handler = handle_keyseq;
id.id_key_handler = handle_key;
id.id_mouse_handler = bind(&xterm_mouse::handle_mouse, &lnav_data.ld_mouse);
id.id_mouse_handler = bind(&xterm_mouse::handle_mouse, &mouse_i);
id.id_unhandled_handler = [](const char *keyseq) {
auto enc_len = lnav_config.lc_ui_keymap.size() * 2;
auto encoded_name = (char *) alloca(enc_len);
@ -1720,6 +1607,7 @@ static void looper()
}
if (initial_build) {
static bool ran_cleanup = false;
vector<pair<Result<string, string>, string>> cmd_results;
execute_init_commands(ec, cmd_results);
@ -1732,6 +1620,11 @@ static void looper()
lnav_data.ld_rl_view->set_alt_value(
last_cmd_result.second);
}
if (!ran_cleanup) {
archive_manager::cleanup_cache();
ran_cleanup = true;
}
}
if (session_stage == 1) {
@ -2189,8 +2082,16 @@ int main(int argc, char *argv[])
}
register_environ_vtab(lnav_data.ld_db.in());
{
static auto vtab_modules =
injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
for (const auto& mod : vtab_modules) {
mod->create(lnav_data.ld_db.in());
}
}
register_views_vtab(lnav_data.ld_db.in());
register_file_vtab(lnav_data.ld_db.in(), lnav_data.ld_active_files);
register_regexp_vtab(lnav_data.ld_db.in());
register_xpath_vtab(lnav_data.ld_db.in());
register_fstat_vtab(lnav_data.ld_db.in());
@ -2697,6 +2598,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
log_info("Executing initial commands");
execute_init_commands(lnav_data.ld_exec_context, cmd_results);
archive_manager::cleanup_cache();
wait_for_pipers();
lnav_data.ld_curl_looper.process_all();
rebuild_indexes();

@ -58,9 +58,7 @@
#include "textfile_sub_source.hh"
#include "log_vtab_impl.hh"
#include "readline_curses.hh"
#include "xterm_mouse.hh"
#include "piper_proc.hh"
#include "term_extra.hh"
#include "curl_looper.hh"
#include "relative_time.hh"
#include "log_format_loader.hh"
@ -279,18 +277,12 @@ struct lnav_data_t {
std::list<pid_t> ld_children;
std::list<std::shared_ptr<piper_proc>> ld_pipers;
xterm_mouse ld_mouse;
term_extra ld_term_extra;
input_state_tracker ld_input_state;
input_dispatcher ld_input_dispatcher;
curl_looper ld_curl_looper;
relative_time ld_last_relative_time;
std::map<std::string, std::vector<script_metadata> > ld_scripts;
exec_context ld_exec_context;
int ld_fifo_counter;
@ -320,15 +312,6 @@ bool update_active_files(const file_collection& new_files);
void wait_for_children();
vis_line_t next_cluster(
vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top);
bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top);
void previous_cluster(bookmark_type_t *bt, textview_curses *tc);
vis_line_t search_forward_from(textview_curses *tc);
textview_curses *get_textview_for_mode(ln_mode_t mode);
#endif

@ -43,6 +43,8 @@
#include <pcrecpp.h>
#include <yajl/api/yajl_tree.h>
#include "bound_tags.hh"
#include "base/injector.hh"
#include "base/string_util.hh"
#include "lnav.hh"
#include "lnav_config.hh"
@ -147,8 +149,6 @@ static Result<string, string> com_adjust_log_time(exec_context &ec, string cmdli
vis_line_t top_line;
struct exttm tm;
std::shared_ptr<logfile> lf;
relative_time rt;
struct relative_time::parse_error pe;
top_line = lnav_data.ld_views[LNV_LOG].get_top();
top_content = lss.at(top_line);
@ -161,8 +161,9 @@ static Result<string, string> com_adjust_log_time(exec_context &ec, string cmdli
dts.set_base_time(top_time.tv_sec);
args[1] = remaining_args(cmdline, args);
if (rt.parse(args[1], pe)) {
new_time = rt.add(top_time).to_timeval();
auto parse_res = relative_time::from_str(args[1]);
if (parse_res.isOk()) {
new_time = parse_res.unwrap().add(top_time).to_timeval();
}
else if (dts.scan(args[1].c_str(), args[1].size(), NULL, &tm, new_time) != NULL) {
// nothing to do
@ -288,21 +289,21 @@ static Result<string, string> com_goto(exec_context &ec, string cmdline, vector<
auto ttt = dynamic_cast<text_time_translator *>(tc->get_sub_source());
int line_number, consumed;
date_time_scanner dts;
struct relative_time::parse_error pe;
relative_time rt;
struct timeval tv;
struct exttm tm;
float value;
nonstd::optional<vis_line_t> dst_vl;
auto parse_res = relative_time::from_str(all_args);
if (rt.parse(all_args, pe)) {
if (parse_res.isOk()) {
if (ttt != nullptr) {
struct timeval tv = ttt->time_for_row(tc->get_top());
vis_line_t vl = tc->get_top(), new_vl;
bool done = false;
auto rt = parse_res.unwrap();
if (rt.is_relative()) {
lnav_data.ld_last_relative_time = rt;
injector::get<relative_time&, last_relative_time_tag>() = rt;
}
do {
@ -2635,18 +2636,17 @@ static Result<string, string> com_pt_time(exec_context &ec, string cmdline, vect
else if (args.size() >= 2) {
string all_args = remaining_args(cmdline, args);
struct timeval new_time = { 0, 0 };
relative_time rt;
struct relative_time::parse_error pe;
date_time_scanner dts;
struct exttm tm;
time_t now;
auto parse_res = relative_time::from_str(all_args);
time(&now);
dts.dts_keep_base_tz = true;
dts.set_base_time(now);
if (rt.parse(all_args, pe)) {
if (parse_res.isOk()) {
tm.et_tm = *gmtime(&now);
rt.add(tm);
parse_res.unwrap().add(tm);
new_time.tv_sec = timegm(&tm.et_tm);
}
else {
@ -3219,11 +3219,10 @@ static Result<string, string> com_hide_line(exec_context &ec, string cmdline, ve
logfile_sub_source &lss = lnav_data.ld_log_source;
date_time_scanner dts;
struct timeval tv;
relative_time rt;
struct relative_time::parse_error pe;
bool tv_set = false;
auto parse_res = relative_time::from_str(all_args);
if (rt.parse(all_args, pe)) {
if (parse_res.isOk()) {
if (tc == &lnav_data.ld_views[LNV_LOG]) {
if (tc->get_inner_height() > 0) {
content_line_t cl;
@ -3235,7 +3234,7 @@ static Result<string, string> com_hide_line(exec_context &ec, string cmdline, ve
cl = lnav_data.ld_log_source.at(vl);
ll = lnav_data.ld_log_source.find_line(cl);
ll->to_exttm(tm);
rt.add(tm);
parse_res.unwrap().add(tm);
tv.tv_sec = timegm(&tm.et_tm);
tv.tv_usec = tm.et_nsec / 1000;
@ -4193,14 +4192,14 @@ static void command_prompt(vector<string> &args)
static void script_prompt(vector<string> &args)
{
textview_curses *tc = *lnav_data.ld_view_stack.top();
map<string, vector<script_metadata>> &scripts = lnav_data.ld_scripts;
auto &scripts = injector::get<available_scripts&>();
lnav_data.ld_mode = LNM_EXEC;
lnav_data.ld_exec_context.ec_top_line = tc->get_top();
lnav_data.ld_rl_view->clear_possibilities(LNM_EXEC, "__command");
find_format_scripts(lnav_data.ld_config_paths, scripts);
for (const auto &iter : scripts) {
for (const auto &iter : scripts.as_scripts) {
lnav_data.ld_rl_view->add_possibility(LNM_EXEC, "__command", iter.first);
}
add_view_text_possibilities(lnav_data.ld_rl_view, LNM_EXEC, "*", tc);

@ -48,6 +48,8 @@
#include "pcrecpp.h"
#include "auto_fd.hh"
#include "base/injector.hh"
#include "base/injector.bind.hh"
#include "base/string_util.hh"
#include "base/lnav_log.hh"
#include "lnav_util.hh"
@ -73,6 +75,10 @@ std::map<intern_string_t, source_location> lnav_config_locations;
lnav_config_listener *lnav_config_listener::LISTENER_LIST;
static auto a = injector::bind<archive_manager::config>::to_instance(+[]() {
return &lnav_config.lc_archive_manager;
});
ghc::filesystem::path dotlnav_path()
{
auto home_env = getenv("HOME");
@ -111,7 +117,7 @@ bool check_experimental(const char *feature_name)
{
const char *env_value = getenv("LNAV_EXP");
require(feature_name != NULL);
require(feature_name != nullptr);
if (env_value && strcasestr(env_value, feature_name)) {
return true;
@ -847,7 +853,21 @@ static struct json_path_container ui_handlers = {
.with_children(theme_defs_handlers),
yajlpp::property_handler("keymap-defs")
.with_description("Keymap definitions.")
.with_children(keymap_defs_handlers)};
.with_children(keymap_defs_handlers),
};
static struct json_path_container archive_handlers = {
yajlpp::property_handler("cache-ttl")
.with_description("The time-to-live for unpacked archives")
.for_field(&_lnav_config::lc_archive_manager,
&archive_manager::config::amc_cache_ttl),
};
static struct json_path_container tuning_handlers = {
yajlpp::property_handler("archive-manager")
.with_description("Settings related to opening archive files")
.with_children(archive_handlers),
};
static set<string> SUPPORTED_CONFIG_SCHEMAS = {
"https://lnav.org/schemas/config-v1.schema.json",
@ -882,6 +902,10 @@ struct json_path_container lnav_config_handlers = json_path_container {
.with_synopsis("The URI of the schema for this file")
.with_description("Specifies the type of this file"),
yajlpp::property_handler("tuning")
.with_description("Internal settings")
.with_children(tuning_handlers),
yajlpp::property_handler("ui")
.with_description("User-interface settings")
.with_children(ui_handlers),

@ -46,6 +46,7 @@
#include "ghc/filesystem.hpp"
#include "lnav_config_fwd.hh"
#include "archive_manager.cfg.hh"
/**
* Compute the path to a file in the user's '.lnav' directory.
@ -98,6 +99,8 @@ struct _lnav_config {
std::map<std::string, lnav_theme> lc_ui_theme_defs;
key_map lc_active_keymap;
archive_manager::config lc_archive_manager;
};
extern struct _lnav_config lnav_config;

@ -1121,7 +1121,7 @@ void extract_metadata_from_file(struct script_metadata &meta_inout)
}
static void find_format_in_path(const ghc::filesystem::path &path,
map<string, vector<script_metadata> > &scripts)
available_scripts& scripts)
{
auto format_path = path / "formats/*/*.lnav";
static_root_mem<glob_t, globfree> gl;
@ -1136,7 +1136,7 @@ static void find_format_in_path(const ghc::filesystem::path &path,
meta.sm_path = gl->gl_pathv[lpc];
meta.sm_name = script_name;
extract_metadata_from_file(meta);
scripts[script_name].push_back(meta);
scripts.as_scripts[script_name].push_back(meta);
log_debug(" found script: %s", meta.sm_path.c_str());
}
@ -1144,7 +1144,7 @@ static void find_format_in_path(const ghc::filesystem::path &path,
}
void find_format_scripts(const vector<ghc::filesystem::path> &extra_paths,
map<string, vector<script_metadata> > &scripts)
available_scripts& scripts)
{
for (const auto &extra_path : extra_paths) {
find_format_in_path(extra_path, scripts);

@ -64,8 +64,13 @@ struct script_metadata {
void extract_metadata_from_file(struct script_metadata &meta_inout);
void find_format_scripts(const std::vector<ghc::filesystem::path> &extra_paths,
std::map<std::string, std::vector<script_metadata> > &scripts);
struct available_scripts {
std::map<std::string, std::vector<script_metadata>> as_scripts;
};
void find_format_scripts(
const std::vector<ghc::filesystem::path> &extra_paths,
available_scripts& scripts);
extern struct json_path_container format_handlers;
extern struct json_path_container root_format_handler;

@ -29,6 +29,7 @@
#include "config.h"
#include "base/injector.hh"
#include "lnav.hh"
#include "lnav_util.hh"
#include "lnav_config.hh"
@ -332,11 +333,11 @@ void rl_change(readline_curses *rc)
string line = rc->get_line_buffer();
size_t name_end = line.find(' ');
string script_name = line.substr(0, name_end);
map<string, vector<script_metadata> > &scripts = lnav_data.ld_scripts;
map<string, vector<script_metadata> >::iterator iter;
auto scripts = injector::get<available_scripts&>();
auto iter = scripts.as_scripts.find(script_name);
if ((iter = scripts.find(script_name)) == scripts.end() ||
iter->second[0].sm_description.empty()) {
if (iter == scripts.as_scripts.end() ||
iter->second[0].sm_description.empty()) {
lnav_data.ld_bottom_source.set_prompt(
"Enter a script to execute: " ABORT_MSG);
}

@ -49,6 +49,7 @@ enum {
};
struct regexp_capture {
static constexpr const char *NAME = "regexp_capture";
static constexpr const char *CREATE_STMT = R"(
-- The regexp_capture() table-valued function allows you to execute a regular-
-- expression over a given string and get the captured data as rows in a table.

@ -91,7 +91,8 @@ const char relative_time::FIELD_CHARS[] = {
'y',
};
bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_out)
Result<relative_time, relative_time::parse_error>
relative_time::from_str(const char *str, size_t len)
{
pcre_input pi(str, 0, len);
pcre_context_static<30> pc;
@ -100,6 +101,8 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
bool next_set = false;
token_t base_token = RTT_INVALID;
rt_field_type last_field_type = RTF__MAX;
relative_time retval;
parse_error pe_out;
pe_out.pe_column = -1;
pe_out.pe_msg.clear();
@ -110,11 +113,11 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
if (pi.pi_next_offset >= pi.pi_length) {
if (number_set) {
pe_out.pe_msg = "Number given without a time unit";
return false;
return Err(pe_out);
}
this->rollover();
return true;
retval.rollover();
return Ok(retval);
}
bool found = false;
@ -130,12 +133,12 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
if (!number_set) {
if (base_token != RTT_INVALID) {
base_token = RTT_INVALID;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_absolute_field_end = RTF__MAX;
continue;
}
if (!this->rt_next && !this->rt_previous) {
if (!retval.rt_next && !retval.rt_previous) {
pe_out.pe_msg = "Expecting a number before time unit";
return false;
return Err(pe_out);
}
}
number_was_set = number_set;
@ -151,30 +154,30 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
gettimeofday(&tv, nullptr);
localtime_r(&tv.tv_sec, &tm.et_tm);
tm.et_nsec = tv.tv_usec * 1000;
this->add(tm);
retval.add(tm);
this->rt_field[RTF_YEARS] = tm.et_tm.tm_year;
this->rt_field[RTF_MONTHS] = tm.et_tm.tm_mon;
this->rt_field[RTF_DAYS] = tm.et_tm.tm_mday;
retval.rt_field[RTF_YEARS] = tm.et_tm.tm_year;
retval.rt_field[RTF_MONTHS] = tm.et_tm.tm_mon;
retval.rt_field[RTF_DAYS] = tm.et_tm.tm_mday;
switch (token) {
case RTT_NOW:
this->rt_field[RTF_HOURS] = tm.et_tm.tm_hour;
this->rt_field[RTF_MINUTES] = tm.et_tm.tm_min;
this->rt_field[RTF_SECONDS] = tm.et_tm.tm_sec;
this->rt_field[RTF_MICROSECONDS] = tm.et_nsec / 1000;
retval.rt_field[RTF_HOURS] = tm.et_tm.tm_hour;
retval.rt_field[RTF_MINUTES] = tm.et_tm.tm_min;
retval.rt_field[RTF_SECONDS] = tm.et_tm.tm_sec;
retval.rt_field[RTF_MICROSECONDS] = tm.et_nsec / 1000;
break;
case RTT_YESTERDAY:
this->rt_field[RTF_DAYS].value -= 1;
retval.rt_field[RTF_DAYS].value -= 1;
case RTT_TODAY:
this->rt_field[RTF_HOURS] = 0;
this->rt_field[RTF_MINUTES] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_HOURS] = 0;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MICROSECONDS] = 0;
break;
default:
break;
}
this->rt_absolute_field_end = RTF__MAX;
retval.rt_absolute_field_end = RTF__MAX;
break;
}
case RTT_INVALID:
@ -186,24 +189,24 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
case RTT_AM:
case RTT_PM:
if (number_set) {
this->rt_field[RTF_HOURS] = number;
this->rt_field[RTF_MINUTES] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_HOURS] = number;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
number_set = false;
}
if (!this->is_absolute(RTF_YEARS)) {
if (!retval.is_absolute(RTF_YEARS)) {
pe_out.pe_msg = "Expecting absolute time with A.M. or P.M.";
return false;
return Err(pe_out);
}
if (token == RTT_AM) {
if (this->rt_field[RTF_HOURS].value == 12) {
this->rt_field[RTF_HOURS] = 0;
if (retval.rt_field[RTF_HOURS].value == 12) {
retval.rt_field[RTF_HOURS] = 0;
}
}
else if (this->rt_field[RTF_HOURS].value < 12) {
this->rt_field[RTF_HOURS].value += 12;
else if (retval.rt_field[RTF_HOURS].value < 12) {
retval.rt_field[RTF_HOURS].value += 12;
}
break;
case RTT_A:
@ -216,173 +219,173 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
case RTT_TIME: {
string hstr = pi.get_substr(pc[0]);
string mstr = pi.get_substr(pc[1]);
this->rt_field[RTF_HOURS] = atoi(hstr.c_str());
this->rt_field[RTF_MINUTES] = atoi(mstr.c_str());
retval.rt_field[RTF_HOURS] = atoi(hstr.c_str());
retval.rt_field[RTF_MINUTES] = atoi(mstr.c_str());
if (pc[2]->is_valid()) {
string sstr = pi.get_substr(pc[2]);
this->rt_field[RTF_SECONDS] = atoi(sstr.c_str());
retval.rt_field[RTF_SECONDS] = atoi(sstr.c_str());
if (pc[3]->is_valid()) {
string substr = pi.get_substr(pc[3]);
switch (substr.length()) {
case 3:
this->rt_field[RTF_MICROSECONDS] =
retval.rt_field[RTF_MICROSECONDS] =
atoi(substr.c_str()) * 1000;
break;
case 6:
this->rt_field[RTF_MICROSECONDS] =
retval.rt_field[RTF_MICROSECONDS] =
atoi(substr.c_str());
break;
}
}
}
else {
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MICROSECONDS] = 0;
}
this->rt_absolute_field_end = RTF__MAX;
retval.rt_absolute_field_end = RTF__MAX;
break;
}
case RTT_NUMBER: {
if (number_set) {
pe_out.pe_msg = "No time unit given for the previous number";
return false;
return Err(pe_out);
}
string numstr = pi.get_substr(pc[0]);
if (sscanf(numstr.c_str(), "%" PRId64, &number) != 1) {
pe_out.pe_msg = "Invalid number: " + numstr;
return false;
return Err(pe_out);
}
number_set = true;
break;
}
case RTT_MICROS:
this->rt_field[RTF_MICROSECONDS] = number;
retval.rt_field[RTF_MICROSECONDS] = number;
break;
case RTT_MILLIS:
this->rt_field[RTF_MICROSECONDS] = number * 1000;
retval.rt_field[RTF_MICROSECONDS] = number * 1000;
break;
case RTT_SECONDS:
if (number_was_set) {
this->rt_field[RTF_SECONDS] = number;
retval.rt_field[RTF_SECONDS] = number;
curr_field_type = RTF_SECONDS;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_MINUTES:
if (number_was_set) {
this->rt_field[RTF_MINUTES] = number;
retval.rt_field[RTF_MINUTES] = number;
curr_field_type = RTF_MINUTES;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_HOURS:
if (number_was_set) {
this->rt_field[RTF_HOURS] = number;
retval.rt_field[RTF_HOURS] = number;
curr_field_type = RTF_HOURS;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MINUTES] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_DAYS:
if (number_was_set) {
this->rt_field[RTF_DAYS] = number;
retval.rt_field[RTF_DAYS] = number;
curr_field_type = RTF_DAYS;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MINUTES] = 0;
this->rt_field[RTF_HOURS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_HOURS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_WEEKS:
this->rt_field[RTF_DAYS] = number * 7;
retval.rt_field[RTF_DAYS] = number * 7;
break;
case RTT_MONTHS:
if (number_was_set) {
this->rt_field[RTF_MONTHS] = number;
retval.rt_field[RTF_MONTHS] = number;
curr_field_type = RTF_MONTHS;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MINUTES] = 0;
this->rt_field[RTF_HOURS] = 0;
this->rt_field[RTF_DAYS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_HOURS] = 0;
retval.rt_field[RTF_DAYS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_YEARS:
if (number_was_set) {
this->rt_field[RTF_YEARS] = number;
retval.rt_field[RTF_YEARS] = number;
curr_field_type = RTF_YEARS;
} else if (next_set) {
this->rt_field[RTF_MICROSECONDS] = 0;
this->rt_field[RTF_SECONDS] = 0;
this->rt_field[RTF_MINUTES] = 0;
this->rt_field[RTF_HOURS] = 0;
this->rt_field[RTF_DAYS] = 0;
this->rt_field[RTF_MONTHS] = 0;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_MICROSECONDS] = 0;
retval.rt_field[RTF_SECONDS] = 0;
retval.rt_field[RTF_MINUTES] = 0;
retval.rt_field[RTF_HOURS] = 0;
retval.rt_field[RTF_DAYS] = 0;
retval.rt_field[RTF_MONTHS] = 0;
retval.rt_absolute_field_end = RTF__MAX;
}
break;
case RTT_BEFORE:
case RTT_AGO:
if (this->empty()) {
if (retval.empty()) {
pe_out.pe_msg = "Expecting a time unit";
return false;
return Err(pe_out);
}
for (int field = 0; field < RTF__MAX; field++) {
if (this->rt_field[field].value > 0) {
this->rt_field[field] = -this->rt_field[field].value;
if (retval.rt_field[field].value > 0) {
retval.rt_field[field] = -retval.rt_field[field].value;
}
if (last_field_type != RTF__MAX && field < last_field_type) {
this->rt_field[field] = 0;
retval.rt_field[field] = 0;
}
}
if (last_field_type != RTF__MAX) {
this->rt_absolute_field_end = last_field_type;
retval.rt_absolute_field_end = last_field_type;
}
break;
case RTT_AFTER:
base_token = token;
break;
case RTT_LATER:
if (this->empty()) {
if (retval.empty()) {
pe_out.pe_msg = "Expecting a time unit before 'later'";
return false;
return Err(pe_out);
}
break;
case RTT_HERE:
break;
case RTT_NEXT:
this->rt_next = true;
retval.rt_next = true;
next_set = true;
break;
case RTT_PREVIOUS:
this->rt_previous = true;
retval.rt_previous = true;
next_set = true;
break;
case RTT_TOMORROW:
this->rt_field[RTF_DAYS] = 1;
retval.rt_field[RTF_DAYS] = 1;
break;
case RTT_NOON:
this->rt_field[RTF_HOURS] = 12;
this->rt_absolute_field_end = RTF__MAX;
retval.rt_field[RTF_HOURS] = 12;
retval.rt_absolute_field_end = RTF__MAX;
for (int lpc2 = RTF_MICROSECONDS;
lpc2 < RTF_HOURS;
lpc2++) {
this->rt_field[lpc2] = 0;
retval.rt_field[lpc2] = 0;
}
break;
@ -402,7 +405,7 @@ bool relative_time::parse(const char *str, size_t len, struct parse_error &pe_ou
if (!found) {
pe_out.pe_msg = "Unrecognized input";
return false;
return Err(pe_out);
}
last_field_type = curr_field_type;
@ -416,21 +419,38 @@ void relative_time::rollover()
continue;
}
int64_t val = this->rt_field[lpc].value;
this->rt_field[lpc] = val % TIME_SCALES[lpc];
this->rt_field[lpc].value = val % TIME_SCALES[lpc];
this->rt_field[lpc + 1].value += val / TIME_SCALES[lpc];
if (this->rt_field[lpc + 1].value) {
this->rt_field[lpc + 1].is_set = true;
}
}
if (std::abs(this->rt_field[RTF_DAYS].value) > 31) {
int64_t val = this->rt_field[RTF_DAYS].value;
this->rt_field[RTF_DAYS] = val % 31;
this->rt_field[RTF_DAYS].value = val % 31;
this->rt_field[RTF_MONTHS].value += val / 31;
if (this->rt_field[RTF_MONTHS].value) {
this->rt_field[RTF_MONTHS].is_set = true;
}
}
if (std::abs(this->rt_field[RTF_MONTHS].value) > 12) {
int64_t val = this->rt_field[RTF_MONTHS].value;
this->rt_field[RTF_MONTHS] = val % 12;
this->rt_field[RTF_MONTHS].value = val % 12;
this->rt_field[RTF_YEARS].value += val / 12;
if (this->rt_field[RTF_YEARS].value) {
this->rt_field[RTF_YEARS].is_set = true;
}
}
}
void relative_time::from_timeval(const struct timeval& tv)
{
this->clear();
this->rt_field[RTF_SECONDS] = tv.tv_sec;
this->rt_field[RTF_MICROSECONDS] = tv.tv_usec;
this->rollover();
}
std::string relative_time::to_string()
{
char dst[128] = "";
@ -499,6 +519,12 @@ std::string relative_time::to_string()
}
}
if (dst[0] == '\0') {
dst[0] = '0';
dst[1] = 's';
dst[2] = '\0';
}
return dst;
}

@ -39,6 +39,7 @@
#include <string>
#include "ptimec.hh"
#include "base/result.h"
class relative_time {
public:
@ -92,6 +93,19 @@ public:
RTF__MAX
};
struct parse_error {
int pe_column;
std::string pe_msg;
};
static Result<relative_time, parse_error>
from_str(const char *str, size_t len);
static Result<relative_time, parse_error>
from_str(const std::string &str) {
return from_str(str.c_str(), str.length());
}
relative_time() {
this->clear();
};
@ -154,17 +168,6 @@ public:
return true;
};
struct parse_error {
int pe_column;
std::string pe_msg;
};
bool parse(const char *str, size_t len, struct parse_error &pe_out);
bool parse(const std::string &str, struct parse_error &pe_out) {
return this->parse(str.c_str(), str.length(), pe_out);
}
struct exttm add_now() {
struct exttm tm;
time_t now;
@ -284,6 +287,8 @@ public:
return retval;
};
void from_timeval(const struct timeval& tv);
void to_timeval(struct timeval &tv_out) {
int64_t us = this->to_microseconds();
@ -291,6 +296,15 @@ public:
tv_out.tv_usec = us % (1000 * 1000);
};
struct timeval to_timeval() {
int64_t us = this->to_microseconds();
struct timeval retval;
retval.tv_sec = us / (1000 * 1000);
retval.tv_usec = us % (1000 * 1000);
return retval;
};
std::string to_string();
void rollover();

@ -6,5 +6,10 @@
"default-colors": true,
"keymap": "default",
"theme": "default"
},
"tuning": {
"archive-manager": {
"cache-ttl": "2d"
}
}
}

@ -462,7 +462,8 @@ void dump_sqlite_schema(sqlite3 *db, std::string &schema_out)
schema_table_list,
schema_table_info,
schema_foreign_key_list,
&schema_out
&schema_out,
{}
};
walk_sqlite_metadata(db, schema_sql_meta_callbacks);
@ -778,7 +779,7 @@ void sql_execute_script(sqlite3 *db,
const char *errmsg;
errmsg = sqlite3_errmsg(db);
errors.push_back(errmsg);
errors.emplace_back(errmsg);
break;
}
}
@ -814,8 +815,6 @@ static struct {
{ SQLITE_INTEGER, "", "123" },
{ SQLITE_FLOAT, "", "123.0" },
{ SQLITE_TEXT, "ipaddress", "127.0.0.1" },
{ SQLITE_NULL }
};
int guess_type_from_pcre(const string &pattern, std::string &collator)
@ -824,16 +823,19 @@ int guess_type_from_pcre(const string &pattern, std::string &collator)
pcrepp re(pattern.c_str());
vector<int> matches;
int retval = SQLITE3_TEXT;
int index = 0;
collator.clear();
for (int lpc = 0; TYPE_TEST_VALUE[lpc].sqlite_type != SQLITE_NULL; lpc++) {
for (const auto& test_value : TYPE_TEST_VALUE) {
pcre_context_static<30> pc;
pcre_input pi(TYPE_TEST_VALUE[lpc].sample);
pcre_input pi(test_value.sample);
if (re.match(pc, pi, PCRE_ANCHORED) &&
pc[0]->c_begin == 0 && pc[0]->length() == (int) pi.pi_length) {
matches.push_back(lpc);
matches.push_back(index);
}
index += 1;
}
if (matches.size() == 1) {

@ -57,7 +57,7 @@ struct sqlite_metadata_callbacks {
sqlite_exec_callback smc_table_list;
sqlite_exec_callback smc_table_info;
sqlite_exec_callback smc_foreign_key_list;
void *smc_userdata;
void *smc_userdata{nullptr};
db_table_map_t smc_db_list;
};

@ -48,18 +48,19 @@ using namespace std;
static string timeslice(const char *time_in, nonstd::optional<const char *> slice_in_opt)
{
const char *slice_in = slice_in_opt.value_or("15m");
relative_time::parse_error pe;
auto parse_res = relative_time::from_str(slice_in, strlen(slice_in));
date_time_scanner dts;
relative_time rt;
time_t now;
time(&now);
dts.set_base_time(now);
if (!rt.parse(slice_in, strlen(slice_in), pe)) {
throw sqlite_func_error("unable to parse time slice value -- {}", slice_in);
if (parse_res.isErr()) {
throw sqlite_func_error("unable to parse time slice value: {} -- {}",
slice_in, parse_res.unwrapErr().pe_msg);
}
auto rt = parse_res.unwrap();
if (rt.empty()) {
throw sqlite_func_error("no time slice value given");
}
@ -94,17 +95,17 @@ nonstd::optional<double> sql_timediff(const char *time1, const char *time2)
{
struct timeval tv1, tv2, retval;
date_time_scanner dts1, dts2;
relative_time rt1, rt2;
struct relative_time::parse_error pe;
auto parse_res1 = relative_time::from_str(time1, -1);
if (rt1.parse(time1, -1, pe)) {
tv1 = rt1.add_now().to_timeval();
if (parse_res1.isOk()) {
tv1 = parse_res1.unwrap().add_now().to_timeval();
} else if (!dts1.convert_to_timeval(time1, -1, nullptr, tv1)) {
return nonstd::nullopt;
}
if (rt2.parse(time2, -1, pe)) {
tv2 = rt2.add_now().to_timeval();
auto parse_res2 = relative_time::from_str(time2, -1);
if (parse_res2.isOk()) {
tv2 = parse_res2.unwrap().add_now().to_timeval();
} else if (!dts2.convert_to_timeval(time2, -1, nullptr, tv2)) {
return nonstd::nullopt;
}

@ -482,3 +482,137 @@ bool ensure_view(textview_curses *expected_tc)
}
return retval;
}
vis_line_t next_cluster(
vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
const vis_line_t top)
{
textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
vis_bookmarks &bm = tc->get_bookmarks();
bookmark_vector<vis_line_t> &bv = bm[bt];
bool top_is_marked = binary_search(bv.begin(), bv.end(), top);
vis_line_t last_top(top), new_top(top), tc_height;
unsigned long tc_width;
int hit_count = 0;
tc->get_dimensions(tc_height, tc_width);
while ((new_top = (bv.*f)(new_top)) != -1) {
int diff = new_top - last_top;
hit_count += 1;
if (!top_is_marked || diff > 1) {
return new_top;
}
else if (hit_count > 1 && std::abs(new_top - top) >= tc_height) {
return vis_line_t(new_top - diff);
}
else if (diff < -1) {
last_top = new_top;
while ((new_top = (bv.*f)(new_top)) != -1) {
if ((std::abs(last_top - new_top) > 1) ||
(hit_count > 1 && (std::abs(top - new_top) >= tc_height))) {
break;
}
last_top = new_top;
}
return last_top;
}
last_top = new_top;
}
if (last_top != top) {
return last_top;
}
return -1_vl;
}
bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top)
{
textview_curses *tc = get_textview_for_mode(lnav_data.ld_mode);
vis_line_t new_top;
new_top = next_cluster(f, bt, top);
if (new_top == -1) {
new_top = next_cluster(f, bt,
tc->is_selectable() ?
tc->get_selection() :
tc->get_top());
}
if (new_top != -1) {
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
lh->loc_history_append(new_top);
};
if (tc->is_selectable()) {
tc->set_selection(new_top);
} else {
tc->set_top(new_top);
}
return true;
}
alerter::singleton().chime();
return false;
}
void previous_cluster(bookmark_type_t *bt, textview_curses *tc)
{
key_repeat_history &krh = lnav_data.ld_key_repeat_history;
vis_line_t height, new_top, initial_top;
unsigned long width;
if (tc->is_selectable()) {
initial_top = tc->get_selection();
} else {
initial_top = tc->get_top();
}
new_top = next_cluster(&bookmark_vector<vis_line_t>::prev,
bt,
initial_top);
tc->get_dimensions(height, width);
if (krh.krh_count > 1 &&
initial_top < (krh.krh_start_line - (1.5 * height)) &&
(initial_top - new_top) < height) {
bookmark_vector<vis_line_t> &bv = tc->get_bookmarks()[bt];
new_top = bv.next(std::max(0_vl, initial_top - height));
}
if (new_top != -1) {
tc->get_sub_source()->get_location_history() | [new_top] (auto lh) {
lh->loc_history_append(new_top);
};
if (tc->is_selectable()) {
tc->set_selection(new_top);
} else {
tc->set_top(new_top);
}
}
else {
alerter::singleton().chime();
}
}
vis_line_t search_forward_from(textview_curses *tc)
{
vis_line_t height, retval =
tc->is_selectable() ? tc->get_selection() : tc->get_top();
key_repeat_history &krh = lnav_data.ld_key_repeat_history;
unsigned long width;
tc->get_dimensions(height, width);
if (krh.krh_count > 1 &&
retval > (krh.krh_start_line + (1.5 * height))) {
retval += vis_line_t(0.90 * height);
}
return retval;
}

@ -34,6 +34,8 @@
#include "help_text.hh"
#include "attr_line.hh"
#include "vis_line.hh"
#include "bookmarks.hh"
class textview_curses;
@ -60,4 +62,14 @@ void layout_views();
void execute_examples();
attr_line_t eval_example(const help_text &ht, const help_example &ex);
vis_line_t next_cluster(
vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top);
bool moveto_cluster(vis_line_t(bookmark_vector<vis_line_t>::*f) (vis_line_t) const,
bookmark_type_t *bt,
vis_line_t top);
void previous_cluster(bookmark_type_t *bt, textview_curses *tc);
vis_line_t search_forward_from(textview_curses *tc);
#endif

@ -34,6 +34,7 @@
#include <string.h>
#include "lnav.hh"
#include "base/injector.bind.hh"
#include "base/lnav_log.hh"
#include "sql_util.hh"
#include "views_vtab.hh"
@ -106,6 +107,7 @@ struct from_sqlite<pair<string, auto_mem<pcre>>> {
};
struct lnav_views : public tvt_iterator_cursor<lnav_views> {
static constexpr const char *NAME = "lnav_views";
static constexpr const char *CREATE_STMT = R"(
-- Access lnav's views through this table.
CREATE TABLE lnav_views (
@ -257,6 +259,7 @@ CREATE TABLE lnav_views (
struct lnav_view_stack : public tvt_iterator_cursor<lnav_view_stack> {
using iterator = vector<textview_curses *>::iterator;
static constexpr const char *NAME = "lnav_view_stack";
static constexpr const char *CREATE_STMT = R"(
-- Access lnav's view stack through this table.
CREATE TABLE lnav_view_stack (
@ -400,6 +403,7 @@ struct lnav_view_filter_base {
struct lnav_view_filters : public tvt_iterator_cursor<lnav_view_filters>,
public lnav_view_filter_base {
static constexpr const char *NAME = "lnav_view_filters";
static constexpr const char *CREATE_STMT = R"(
-- Access lnav's filters through this table.
CREATE TABLE lnav_view_filters (
@ -549,6 +553,7 @@ CREATE TABLE lnav_view_filters (
struct lnav_view_filter_stats : public tvt_iterator_cursor<lnav_view_filter_stats>,
public lnav_view_filter_base {
static constexpr const char *NAME = "lnav_view_filter_stats";
static constexpr const char *CREATE_STMT = R"(
-- Access statistics for filters through this table.
CREATE TABLE lnav_view_filter_stats (
@ -583,6 +588,7 @@ CREATE TABLE lnav_view_filter_stats (
};
struct lnav_view_files : public tvt_iterator_cursor<lnav_view_files> {
static constexpr const char *NAME = "lnav_view_files";
static constexpr const char *CREATE_STMT = R"(
--
CREATE TABLE lnav_view_files (
@ -648,35 +654,19 @@ CREATE VIEW lnav_view_filters_and_stats AS
SELECT * FROM lnav_view_filters LEFT NATURAL JOIN lnav_view_filter_stats
)";
static auto a = injector::bind_multiple<vtab_module_base>()
.add<vtab_module<lnav_views>>()
.add<vtab_module<lnav_view_stack>>()
.add<vtab_module<lnav_view_filters>>()
.add<vtab_module<tvt_no_update<lnav_view_filter_stats>>>()
.add<vtab_module<lnav_view_files>>();
int register_views_vtab(sqlite3 *db)
{
static vtab_module<lnav_views> LNAV_VIEWS_MODULE;
static vtab_module<lnav_view_stack> LNAV_VIEW_STACK_MODULE;
static vtab_module<lnav_view_filters> LNAV_VIEW_FILTERS_MODULE;
static vtab_module<tvt_no_update<lnav_view_filter_stats>> LNAV_VIEW_FILTER_STATS_MODULE;
static vtab_module<lnav_view_files> LNAV_VIEW_FILES_MODULE;
int rc;
rc = LNAV_VIEWS_MODULE.create(db, "lnav_views");
assert(rc == SQLITE_OK);
rc = LNAV_VIEW_STACK_MODULE.create(db, "lnav_view_stack");
assert(rc == SQLITE_OK);
rc = LNAV_VIEW_FILTERS_MODULE.create(db, "lnav_view_filters");
assert(rc == SQLITE_OK);
rc = LNAV_VIEW_FILTER_STATS_MODULE.create(db, "lnav_view_filter_stats");
assert(rc == SQLITE_OK);
rc = LNAV_VIEW_FILES_MODULE.create(db, "lnav_view_files");
assert(rc == SQLITE_OK);
char *errmsg;
if (sqlite3_exec(db, CREATE_FILTER_VIEW, nullptr, nullptr, &errmsg) != SQLITE_OK) {
log_error("Unable to create filter view: %s", errmsg);
}
return rc;
return 0;
}

@ -471,8 +471,14 @@ private:
int viu_max_column;
};
struct vtab_module_base {
virtual int create(sqlite3 *db) = 0;
virtual ~vtab_module_base() = default;
};
template<typename T>
struct vtab_module {
struct vtab_module : public vtab_module_base {
struct vtab {
explicit vtab(T& impl) : v_impl(impl) {};
@ -664,6 +670,8 @@ struct vtab_module {
this->addUpdate<T>(this->vm_impl);
};
~vtab_module() override = default;
int create(sqlite3 *db, const char *name)
{
auto impl_name = std::string(name);
@ -680,6 +688,10 @@ struct vtab_module {
return sqlite3_exec(db, create_stmt.c_str(), nullptr, nullptr, nullptr);
};
int create(sqlite3 *db) override {
return this->create(db, T::NAME);
}
sqlite3_module vm_module;
T vm_impl;
};

@ -128,6 +128,7 @@ std::string get_actual_path(const pugi::xml_node& node)
}
struct xpath_vtab {
static constexpr const char *NAME = "xpath";
static constexpr const char *CREATE_STMT = R"(
-- The xpath() table-valued function allows you to execute an xpath expression
CREATE TABLE xpath (
@ -172,7 +173,7 @@ CREATE TABLE xpath (
};
int eof() {
return this->c_rowid >= this->c_results.size();
return this->c_rowid >= (int64_t) this->c_results.size();
};
int get_rowid(sqlite3_int64 &rowid_out) {

@ -32,7 +32,10 @@
#ifndef yajlpp_def_hh
#define yajlpp_def_hh
#include <chrono>
#include "yajlpp.hh"
#include "relative_time.hh"
#define FOR_FIELD(T, FIELD) \
for_field<T, decltype(T :: FIELD), & T :: FIELD>()
@ -491,6 +494,70 @@ struct json_path_handler : public json_path_handler_base {
return *this;
}
template<
typename... Args,
std::enable_if_t<LastIs<std::chrono::seconds, Args...>::value, bool> = true
>
json_path_handler &for_field(Args... args) {
this->add_cb(str_field_cb2);
this->jph_str_cb = [args...](yajlpp_parse_context *ypc,
const unsigned char *str,
size_t len) {
auto obj = ypc->ypc_obj_stack.top();
auto handler = ypc->ypc_current_handler;
auto parse_res = relative_time::from_str((const char *) str, len);
if (parse_res.isErr()) {
ypc->report_error(lnav_log_level_t::ERROR,
"error:%s:line %d\n"
" Invalid duration: '%.*s' -- %s",
ypc->ypc_source.c_str(),
ypc->get_line_number(),
len,
str,
parse_res.unwrapErr().pe_msg.c_str());
ypc->report_error(lnav_log_level_t::ERROR,
" for option: %s %s -- %s\n",
&ypc->ypc_path[0],
handler->jph_synopsis,
handler->jph_description);
return 1;
}
json_path_handler::get_field(obj, args...) = std::chrono::seconds(
parse_res.template unwrap().to_timeval().tv_sec);
return 1;
};
this->jph_gen_callback = [args...](yajlpp_gen_context &ygc,
const json_path_handler_base &jph,
yajl_gen handle) {
const auto& field = json_path_handler::get_field(ygc.ygc_obj_stack.top(), args...);
if (!ygc.ygc_default_stack.empty()) {
const auto& field_def = json_path_handler::get_field(ygc.ygc_default_stack.top(), args...);
if (field == field_def) {
return yajl_gen_status_ok;
}
}
if (ygc.ygc_depth) {
yajl_gen_string(handle, jph.jph_property);
}
yajlpp_generator gen(handle);
relative_time rt;
rt.from_timeval({ field.count() });
return gen(rt.to_string());
};
this->jph_field_getter = [args...](void *root, nonstd::optional<std::string> name) {
return (void *) &json_path_handler::get_field(root, args...);
};
return *this;
}
template<
typename... Args,
std::enable_if_t<LastIsEnum<Args...>::value, bool> = true

@ -380,6 +380,7 @@ DISTCLEANFILES = \
logfile_syslog_fr_test.0 \
logfile_syslog_with_mixed_times_test.0 \
test-logs.tgz \
test-logs-trunc.tgz \
test_pretty_in.* \
tmp \
unreadable.log \

@ -59,6 +59,11 @@ void rebuild_indexes()
{
}
textview_curses *get_textview_for_mode(ln_mode_t mode)
{
return nullptr;
}
readline_context::command_map_t lnav_commands;
int main(int argc, char *argv[])

@ -63,6 +63,11 @@ void rebuild_indexes()
{
}
textview_curses *get_textview_for_mode(ln_mode_t mode)
{
return nullptr;
}
readline_context::command_map_t lnav_commands;
int main(int argc, char *argv[])

@ -34,22 +34,19 @@
#include "base/string_util.hh"
static struct test_data {
const char *str;
const char *abbrev_str;
size_t max_len;
const char *str{nullptr};
const char *abbrev_str{nullptr};
size_t max_len{0};
} TEST_DATA[] = {
{ "abc", "abc", 5 },
{ "com.example.foo.bar", "c.e.f.bar", 5 },
{ "com.example.foo.bar", "c.e.foo.bar", 15 },
{ "no dots in here", "no dots in here", 5 },
{ nullptr }
};
int main(int argc, char *argv[])
{
for (int lpc = 0; TEST_DATA[lpc].str; lpc++) {
test_data &td = TEST_DATA[lpc];
for (const auto& td : TEST_DATA) {
char buffer[1024];
strcpy(buffer, td.str);

@ -32,6 +32,7 @@ warning: unexpected path --
warning: /ui
warning: accepted paths --
warning: \$schema The URI of the schema for this file -- Specifies the type of this file
warning: tuning -- Internal settings
warning: ui -- User-interface settings
warning: global -- Global variable definitions
warning:formats/invalid-config/config.truncated.json:line 2
@ -39,6 +40,7 @@ warning: unexpected path --
warning: /ui
warning: accepted paths --
warning: \$schema The URI of the schema for this file -- Specifies the type of this file
warning: tuning -- Internal settings
warning: ui -- User-interface settings
warning: global -- Global variable definitions
error:formats/invalid-config/config.malformed.json:3:invalid json -- parse error: object key and value must be separated by a colon (':')

@ -42,6 +42,7 @@ if test x"${LIBARCHIVE_LIBS}" != x""; then
${XZ_CMD} -z -c ${srcdir}/logfile_syslog.1 > logfile_syslog.1.xz
run_test env TMPDIR=tmp ${lnav_test} -n \
-c ':config /tuning/archive-manager/cache-ttl 1d' \
logfile_syslog.1.xz
check_output "decompression not working" <<EOF
@ -57,7 +58,9 @@ EOF
dd if=test-logs.tgz of=test-logs-trunc.tgz bs=4096 count=20
mkdir -p tmp
run_test env TMPDIR=tmp ${lnav_test} -n test-logs.tgz
run_test env TMPDIR=tmp ${lnav_test} \
-c ':config /tuning/archive-manager/cache-ttl 1d' \
-n test-logs.tgz
check_output "archive not unpacked" <<EOF
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
@ -66,17 +69,26 @@ EOF
10.112.81.15 - - [15/Feb/2013:06:00:31 +0000] "-" 400 0 "-" "-"
EOF
if ! test tmp/*/test-logs.tgz/logfile_access_log.0; then
if ! test -f tmp/*/*-test-logs.tgz/logfile_access_log.0; then
echo "archived file not unpacked"
exit 1
fi
if test -w tmp/*/test-logs.tgz/logfile_access_log.0; then
if test -w tmp/*/*-test-logs.tgz/logfile_access_log.0; then
echo "archived file is writable"
exit 1
fi
run_test env TMPDIR=tmp ${lnav_test} -n \
env TMPDIR=tmp ${lnav_test} -d /tmp/lnav.err \
-c ':config /tuning/archive-manager/cache-ttl 0d' \
-n -q ${srcdir}/logfile_syslog.0
if test -f tmp/lnav*/*-test-logs.tgz/logfile_access_log.0; then
echo "archive cache not deleted?"
exit 1
fi
run_test env TMPDIR=tmp ${lnav_test} -n\
-c ';SELECT view_name, basename(filepath), visible FROM lnav_view_files' \
test-logs.tgz

@ -44,6 +44,7 @@ static struct {
const char *expected;
} TEST_DATA[] = {
// { "10 minutes after the next hour", "next 0:10" },
{ "0s", "0s" },
{ "next day", "next day 0:00" },
{ "next month", "next month day 0 0:00" },
{ "next year", "next year month 0 day 0 0:00" },
@ -65,7 +66,7 @@ static struct {
{ "12pm", "12:00" },
{ "00:27:18.567", "0:27:18.567" },
{ NULL, NULL }
{ nullptr, nullptr }
};
static struct {
@ -84,66 +85,52 @@ TEST_CASE("reltime")
time_t base_time = 1317913200;
struct exttm base_tm;
base_tm.et_tm = *gmtime(&base_time);
struct relative_time::parse_error pe;
struct timeval tv;
struct exttm tm, tm2;
time_t new_time;
relative_time rt;
for (int lpc = 0; TEST_DATA[lpc].reltime; lpc++) {
bool rc;
rt.clear();
rc = rt.parse(TEST_DATA[lpc].reltime, pe);
CHECK_MESSAGE(rc, TEST_DATA[lpc].reltime);
CHECK(std::string(TEST_DATA[lpc].expected) == rt.to_string());
auto res = relative_time::from_str(TEST_DATA[lpc].reltime);
CHECK_MESSAGE(res.isOk(), TEST_DATA[lpc].reltime);
CHECK(std::string(TEST_DATA[lpc].expected) == res.unwrap().to_string());
}
for (int lpc = 0; BAD_TEST_DATA[lpc].reltime; lpc++) {
bool rc;
rt.clear();
rc = rt.parse(BAD_TEST_DATA[lpc].reltime, pe);
CHECK(!rc);
CHECK(pe.pe_msg == string(BAD_TEST_DATA[lpc].expected_error));
auto res = relative_time::from_str(BAD_TEST_DATA[lpc].reltime);
CHECK(res.isErr());
CHECK(res.unwrapErr().pe_msg == string(BAD_TEST_DATA[lpc].expected_error));
}
rt.clear();
rt.parse("", pe);
rt = relative_time::from_str("").unwrap();
CHECK(rt.empty());
rt.clear();
rt.parse("a minute ago", pe);
rt = relative_time::from_str("a minute ago").unwrap();
CHECK(rt.rt_field[relative_time::RTF_MINUTES].value == -1);
rt.clear();
rt.parse("5 milliseconds", pe);
rt = relative_time::from_str("5 milliseconds").unwrap();
CHECK(rt.rt_field[relative_time::RTF_MICROSECONDS].value == 5 * 1000);
rt.clear();
rt.parse("5000 ms ago", pe);
rt = relative_time::from_str("5000 ms ago").unwrap();
CHECK(rt.rt_field[relative_time::RTF_SECONDS].value == -5);
rt.clear();
rt.parse("5 hours 20 minutes ago", pe);
rt = relative_time::from_str("5 hours 20 minutes ago").unwrap();
CHECK(rt.rt_field[relative_time::RTF_HOURS].value == -5);
CHECK(rt.rt_field[relative_time::RTF_MINUTES].value == -20);
rt.clear();
rt.parse("5 hours and 20 minutes ago", pe);
rt = relative_time::from_str("5 hours and 20 minutes ago").unwrap();
CHECK(rt.rt_field[relative_time::RTF_HOURS].value == -5);
CHECK(rt.rt_field[relative_time::RTF_MINUTES].value == -20);
rt.clear();
rt.parse("1:23", pe);
rt = relative_time::from_str("1:23").unwrap();
CHECK(rt.rt_field[relative_time::RTF_HOURS].value == 1);
CHECK(rt.rt_field[relative_time::RTF_MINUTES].value == 23);
CHECK(rt.is_absolute());
rt.clear();
rt.parse("1:23:45", pe);
rt = relative_time::from_str("1:23:45").unwrap();
CHECK(rt.rt_field[relative_time::RTF_HOURS].value == 1);
CHECK(rt.rt_field[relative_time::RTF_MINUTES].value == 23);
@ -158,8 +145,7 @@ TEST_CASE("reltime")
CHECK(tm.et_tm.tm_hour == 1);
CHECK(tm.et_tm.tm_min == 23);
rt.clear();
rt.parse("5 minutes ago", pe);
rt = relative_time::from_str("5 minutes ago").unwrap();
tm = base_tm;
rt.add(tm);
@ -168,8 +154,7 @@ TEST_CASE("reltime")
CHECK(new_time == (base_time - (5 * 60)));
rt.clear();
rt.parse("today at 4pm", pe);
rt = relative_time::from_str("today at 4pm").unwrap();
memset(&tm, 0, sizeof(tm));
memset(&tm2, 0, sizeof(tm2));
gettimeofday(&tv, NULL);
@ -194,8 +179,7 @@ TEST_CASE("reltime")
CHECK(tm.et_tm.tm_min == tm2.et_tm.tm_min);
CHECK(tm.et_tm.tm_sec == tm2.et_tm.tm_sec);
rt.clear();
rt.parse("yesterday at 4pm", pe);
rt = relative_time::from_str("yesterday at 4pm").unwrap();
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm.et_tm);
localtime_r(&tv.tv_sec, &tm2.et_tm);
@ -219,8 +203,7 @@ TEST_CASE("reltime")
CHECK(tm.et_tm.tm_min == tm2.et_tm.tm_min);
CHECK(tm.et_tm.tm_sec == tm2.et_tm.tm_sec);
rt.clear();
rt.parse("2 days ago", pe);
rt = relative_time::from_str("2 days ago").unwrap();
gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm.et_tm);
localtime_r(&tv.tv_sec, &tm2.et_tm);

@ -828,13 +828,13 @@ check_output "schema view is not working" <<EOF
ATTACH DATABASE '' AS 'main';
CREATE VIRTUAL TABLE environ USING environ_vtab_impl();
CREATE VIRTUAL TABLE lnav_views USING lnav_views_impl();
CREATE VIRTUAL TABLE lnav_view_stack USING lnav_view_stack_impl();
CREATE VIRTUAL TABLE lnav_view_filters USING lnav_view_filters_impl();
CREATE VIRTUAL TABLE lnav_view_filter_stats USING lnav_view_filter_stats_impl();
CREATE VIRTUAL TABLE lnav_view_files USING lnav_view_files_impl();
CREATE VIRTUAL TABLE lnav_view_stack USING lnav_view_stack_impl();
CREATE VIRTUAL TABLE lnav_view_filters USING lnav_view_filters_impl();
CREATE VIRTUAL TABLE lnav_file USING lnav_file_impl();
CREATE VIEW lnav_view_filters_and_stats AS
SELECT * FROM lnav_view_filters LEFT NATURAL JOIN lnav_view_filter_stats;
CREATE VIRTUAL TABLE lnav_file USING lnav_file_impl();
CREATE VIRTUAL TABLE regexp_capture USING regexp_capture_impl();
CREATE VIRTUAL TABLE xpath USING xpath_impl();
CREATE VIRTUAL TABLE fstat USING fstat_impl();

Loading…
Cancel
Save