Add import functionality
This commit is contained in:
parent
2a0bffd8bb
commit
454d23a975
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
class LogcatEntries; // forward declaration from ../logcat_entry.h
|
||||
|
||||
void export_fragment(const LogcatEntries& logcat_entries);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
class LogcatEntries; // forward declaration from ../logcat_entry.h
|
||||
|
||||
void import_fragment(LogcatEntries& logcat_entries);
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue