Add import functionality

This commit is contained in:
blankie 2023-03-30 22:56:43 +07:00
parent 2a0bffd8bb
commit 454d23a975
Signed by: blankie
GPG Key ID: CC15FC822C7F61F5
10 changed files with 220 additions and 22 deletions

View File

@ -19,7 +19,7 @@ set(DEFINITIONS "")
# imgui include because https://github.com/ocornut/imgui/issues/6184#issuecomment-1439570929
list(APPEND INCLUDES thirdparty thirdparty/imgui /usr/include/SDL2)
list(APPEND SOURCES main.cpp event_loop.cpp logcat_thread.cpp logcat_entry.cpp log.cpp config.cpp filters.cpp misc.cpp pcre2_wrapper.cpp
group_panel.cpp fragments/filters.cpp fragments/export.cpp windows/logs.cpp windows/settings.cpp windows/main.cpp)
group_panel.cpp fragments/filters.cpp fragments/export.cpp fragments/import.cpp windows/logs.cpp windows/settings.cpp windows/main.cpp)
list(APPEND IMGUI_SOURCES thirdparty/imgui/imgui.cpp thirdparty/imgui/imgui_draw.cpp thirdparty/imgui/imgui_widgets.cpp thirdparty/imgui/imgui_tables.cpp
thirdparty/imgui/misc/cpp/imgui_stdlib.cpp thirdparty/imgui/misc/freetype/imgui_freetype.cpp
thirdparty/imgui/backends/imgui_impl_sdl2.cpp thirdparty/imgui/backends/imgui_impl_opengl3.cpp)

View File

@ -13,7 +13,7 @@ enum class ExportAmount : int {
static inline void export_logcat_entries(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers, File& file);
static inline void export_logcat_entries_to_clipboard(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers);
static inline void export_logcat_entries(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers, auto cb);
static void export_logcat_entries(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers, auto cb);
static inline std::optional<File> save_file_picker();
void export_fragment(const LogcatEntries& logcat_entries) {
@ -64,7 +64,7 @@ static inline void export_logcat_entries_to_clipboard(const LogcatEntries& logca
ImGui::SetClipboardText(text.c_str());
}
static inline void export_logcat_entries(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers, auto cb) {
static void export_logcat_entries(const LogcatEntries& logcat_entries, ExportAmount export_amount, bool export_buffers, auto cb) {
size_t size;
const LogcatEntry& (LogcatEntries::*get_fn)(size_t) const;
unsigned int buffers_printed = 0;

View File

@ -1,7 +1,5 @@
#pragma once
#include <vector>
class LogcatEntries; // forward declaration from ../logcat_entry.h
void export_fragment(const LogcatEntries& logcat_entries);

154
fragments/import.cpp Normal file
View File

@ -0,0 +1,154 @@
#include <imgui/imgui.h>
#include <nativefiledialog-extended/src/include/nfd.h>
#include <optional>
#include "../file.h"
#include "../logcat_entry.h"
static inline void import_logcat_entries(LogcatEntries& logcat_entries, bool ignore_buffer_lines, File& file);
static inline void import_logcat_entries_from_clipboard(LogcatEntries& logcat_entries, bool ignore_buffer_lines);
static void import_logcat_entries(LogcatEntries& logcat_entries, bool ignore_buffer_lines, auto cb);
static inline std::optional<File> open_file_picker();
void import_fragment(LogcatEntries& logcat_entries) {
static bool ignore_buffer_lines = false;
ImGui::Checkbox("Ignore buffer lines", &ignore_buffer_lines);
if (ImGui::Button("Import from File")) {
std::optional<File> file = open_file_picker();
if (file) {
ImGui::CloseCurrentPopup();
try {
import_logcat_entries(logcat_entries, ignore_buffer_lines, *file);
} catch (const std::exception& e) {
log(std::string("Failed to import logcat entries: ") + e.what());
}
}
}
ImGui::SameLine();
if (ImGui::Button("Import from Clipboard")) {
ImGui::CloseCurrentPopup();
try {
import_logcat_entries_from_clipboard(logcat_entries, ignore_buffer_lines);
} catch (const std::exception& e) {
log(std::string("Failed to import logcat entries: ") + e.what());
}
}
}
static inline void import_logcat_entries(LogcatEntries& logcat_entries, bool ignore_buffer_lines, File& file) {
auto handle_valid_char = [](char line[MAX_LOGCAT_LINE_SIZE], size_t* line_length, char c) -> std::optional<bool> {
if (c == '\n') {
return true;
}
if (*line_length == MAX_LOGCAT_LINE_SIZE) {
throw std::runtime_error("Received line longer than 512k");
}
line[*line_length] = c;
(*line_length)++;
return std::nullopt;
};
auto handle_char = [&](char line[MAX_LOGCAT_LINE_SIZE], size_t* line_length, int c) -> std::optional<bool> {
if (c != EOF) {
return handle_valid_char(line, line_length, static_cast<char>(c));
}
if (feof(file.get())) {
return *line_length != 0;
} else if (ferror(file.get())) {
throw_system_error("fread()");
} else {
throw std::runtime_error("fgetc() returned EOF without setting anything");
}
return std::nullopt;
};
import_logcat_entries(logcat_entries, ignore_buffer_lines, [&](char line[MAX_LOGCAT_LINE_SIZE], size_t* line_length) {
*line_length = 0;
while (true) {
int c = fgetc(file.get());
std::optional<bool> ret = handle_char(line, line_length, c);
if (ret) {
return *ret;
}
}
});
}
static inline void import_logcat_entries_from_clipboard(LogcatEntries& logcat_entries, bool ignore_buffer_lines) {
const char* text = ImGui::GetClipboardText();
import_logcat_entries(logcat_entries, ignore_buffer_lines, [&](char line[MAX_LOGCAT_LINE_SIZE], size_t* line_length) {
if (!text) {
return false;
}
const char* newline_offset = strchr(text, '\n');
*line_length = newline_offset ? static_cast<size_t>(newline_offset - text) : strlen(text);
if (*line_length > MAX_LOGCAT_LINE_SIZE) {
throw std::runtime_error("Received line longer than 512k");
}
memcpy(line, text, *line_length);
text = newline_offset ? &newline_offset[1] : nullptr;
return true;
});
}
static void import_logcat_entries(LogcatEntries& logcat_entries, bool ignore_buffer_lines, auto cb) {
char line[MAX_LOGCAT_LINE_SIZE];
size_t line_length;
Buffer buffer = Buffer::Unknown;
while (cb(line, &line_length)) {
if (line_length == 0) {
continue;
}
std::optional<LogcatEntry> logcat_entry = try_parse_logcat_entry(line, line_length, buffer);
if (logcat_entry) {
logcat_entries.push_back(std::move(*logcat_entry));
continue;
}
std::optional<Buffer> new_buffer = try_parse_buffer(line, line_length);
if (new_buffer) {
if (!ignore_buffer_lines) {
buffer = *new_buffer;
}
continue;
}
throw std::invalid_argument(std::string("Cannot parse line: ") + std::string(line, line_length));
}
}
static inline std::optional<File> open_file_picker() {
nfdchar_t* path;
nfdfilteritem_t filters[1] = {{"Log file", "log"}};
nfdresult_t res = NFD_OpenDialog(&path, filters, 1, nullptr);
if (res == NFD_OKAY) {
try {
File file(path, "r");
NFD_FreePath(path);
return std::move(file);
} catch (const std::exception& e) {
NFD_FreePath(path);
log(std::string("Failed to open file from file picker: ") + e.what());
}
} else if (res == NFD_CANCEL) {
// dialog was canceled, shrug
} else {
// ignore error when failing to open the file picker
}
return std::nullopt;
}

5
fragments/import.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
class LogcatEntries; // forward declaration from ../logcat_entry.h
void import_fragment(LogcatEntries& logcat_entries);

View File

@ -8,7 +8,7 @@
#include "pcre2_wrapper.h"
static const Pcre2Regex LogcatEntryRegex(
"^ *(\\d+)(?:\\.\\d+)?" // time
"^ *(?:(\\d+)(?:\\.\\d+)?|(\\d{4,})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}))" // time and date
"(?: +([\\w\\d._-]+))?" // optional user
" +(\\d+)" // pid
" +(\\d+)" // tid
@ -22,6 +22,7 @@ static inline char to_char(Priority priority);
static inline std::string rightpad(const std::string& str, size_t characters);
static inline Priority priority_from(char c);
static inline int to_int(const char* str, const char* expected_end);
static inline long to_long(const char* str, const char* expected_end);
static unsigned long long to_ull(const char* str, const char* expected_end);
@ -78,7 +79,7 @@ std::string to_string(const LogcatEntry& logcat_entry) {
std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buffer buffer) {
regmatch_t matches[8];
regmatch_t matches[14];
matches[0].rm_so = 0;
matches[0].rm_eo = static_cast<regoff_t>(length);
if (static_cast<size_t>(matches[0].rm_eo) != length) {
@ -90,6 +91,7 @@ std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buff
}
auto group_exists = [](const regmatch_t& match) { return match.rm_so > -1 && match.rm_eo > -1; };
auto group_int = [&](const regmatch_t& match) { return to_int(&buf[match.rm_so], &buf[match.rm_eo]); };
auto group_long = [&](const regmatch_t& match) { return to_long(&buf[match.rm_so], &buf[match.rm_eo]); };
auto group_ull = [&](const regmatch_t& match) { return to_ull(&buf[match.rm_so], &buf[match.rm_eo]); };
auto group_string = [&](const regmatch_t& match) { return std::string(&buf[match.rm_so], static_cast<size_t>(match.rm_eo - match.rm_so)); };
@ -98,17 +100,33 @@ std::optional<LogcatEntry> try_parse_logcat_entry(char* buf, size_t length, Buff
// if pcre2 gives us negative offsets then i'll die
LogcatEntry logcat_entry = {
.buffer = buffer,
// time to be set at the end
.user = group_exists(matches[2]) ? std::optional(group_string(matches[2])) : std::nullopt,
.pid = group_ull(matches[3]),
.tid = group_ull(matches[4]),
.priority = group_priority(matches[5]),
.tag = group_string(matches[6]),
.message = group_string(matches[7]),
// time to be set later
.user = group_exists(matches[8]) ? std::optional(group_string(matches[8])) : std::nullopt,
.pid = group_ull(matches[9]),
.tid = group_ull(matches[10]),
.priority = group_priority(matches[11]),
.tag = group_string(matches[12]),
.message = group_string(matches[13]),
};
time_t time = group_long(matches[1]);
localtime_r(&time, &logcat_entry.time);
if (group_exists(matches[1])) {
time_t time = group_long(matches[1]);
localtime_r(&time, &logcat_entry.time);
} else {
struct tm tm = {
.tm_sec = group_int(matches[7]),
.tm_min = group_int(matches[6]),
.tm_hour = group_int(matches[5]),
.tm_mday = group_int(matches[4]),
.tm_mon = group_int(matches[3]),
.tm_year = group_int(matches[2]) - 1900,
.tm_isdst = -1,
.tm_gmtoff = 0,
};
time_t time = mktime(&tm) + tm.tm_gmtoff; // for some reason you have to add the local time offset?
localtime_r(&time, &logcat_entry.time);
}
return std::move(logcat_entry);
}
@ -237,6 +255,16 @@ static inline Priority priority_from(char c) {
}
}
static inline int to_int(const char* str, const char* expected_end) {
long res = to_long(str, expected_end);
if (res > INT_MAX) {
throw std::overflow_error(std::string(str) + " is too big");
} else if (res < INT_MIN) {
throw std::underflow_error(std::string(str) + " is too small");
}
return static_cast<int>(res);
}
static inline long to_long(const char* str, const char* expected_end) {
char* endptr;

View File

@ -7,6 +7,8 @@
struct Config; // forward declaration from config.h
#define MAX_LOGCAT_LINE_SIZE 512 * 1024
enum class Buffer {
Unknown = 0b1,
Main = 0b10,

View File

@ -26,7 +26,7 @@ static void mark_nonblock(int fd) {
}
}
static inline void look_for_newlines(char buf[NEWLINE_BUF_SIZE], size_t* used, size_t previous_used,
static inline void look_for_newlines(char buf[MAX_LOGCAT_LINE_SIZE], size_t* used, size_t previous_used,
void(LogcatThread::*handle_line)(char*, size_t, bool), LogcatThread* logcat_thread, bool is_stdout) {
size_t search_offset = previous_used;
size_t real_offset = 0;
@ -57,7 +57,7 @@ static inline void look_for_newlines(char buf[NEWLINE_BUF_SIZE], size_t* used, s
static inline void handle_fd(int fd, char* buf, size_t* used,
void(LogcatThread::*handle_line)(char*, size_t, bool), LogcatThread* logcat_thread, bool is_stdout) {
while (true) {
ssize_t read_size_u = read(fd, &buf[*used], (NEWLINE_BUF_SIZE - *used) * sizeof(char));
ssize_t read_size_u = read(fd, &buf[*used], (MAX_LOGCAT_LINE_SIZE - *used) * sizeof(char));
if (read_size_u == 0) {
return;
} else if (read_size_u < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
@ -66,7 +66,7 @@ static inline void handle_fd(int fd, char* buf, size_t* used,
throw_system_error("read()");
}
size_t read_size = static_cast<size_t>(read_size_u) / sizeof(char);
if (*used + read_size > NEWLINE_BUF_SIZE) {
if (*used + read_size > MAX_LOGCAT_LINE_SIZE) {
throw std::runtime_error("Received line longer than 512k");
}
size_t previous_used = *used;

View File

@ -7,7 +7,6 @@
#include "logcat_entry.h"
typedef std::variant<LogEntry, LogcatEntry> LogcatThreadItem;
#define NEWLINE_BUF_SIZE 512 * 1024
enum class LogcatProcessRequest {
None,
@ -54,9 +53,9 @@ private:
int _stderr_read_fd = -1;
int _stderr_write_fd = -1;
char _stdout_buf[NEWLINE_BUF_SIZE];
char _stdout_buf[MAX_LOGCAT_LINE_SIZE];
size_t _stdout_buf_used = 0;
char _stderr_buf[NEWLINE_BUF_SIZE];
char _stderr_buf[MAX_LOGCAT_LINE_SIZE];
size_t _stderr_buf_used = 0;
pid_t _logcat_pid = -1;

View File

@ -9,6 +9,7 @@
#include "../logcat_entry.h"
#include "../logcat_thread.h"
#include "../fragments/export.h"
#include "../fragments/import.h"
#include "main.h"
static inline void render_table_item_context_menu(const LogcatEntry& logcat_entry) {
@ -164,10 +165,12 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, LogcatThr
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if (ImGui::Button("Clear")) {
logcat_entries.clear();
}
ImGui::SameLine();
if (ImGui::Button("Export")) {
ImGui::OpenPopup("export_logcat");
@ -177,6 +180,15 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, LogcatThr
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Button("Import")) {
ImGui::OpenPopup("import_logcat");
}
if (ImGui::BeginPopup("import_logcat")) {
import_fragment(logcat_entries);
ImGui::EndPopup();
}
ImGui::Separator();
// copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole()
// and [SECTION] Example App: Long Text / ShowExampleAppLongText()