diff --git a/src/basic/win32.cpp b/src/basic/win32.cpp index 23ec850..65d0d4a 100644 --- a/src/basic/win32.cpp +++ b/src/basic/win32.cpp @@ -57,10 +57,16 @@ Process RunEx(String args) { DWORD dw = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); - LocalFree(lpMsgBuf); + + char *buff = (char *)lpMsgBuf; + size_t buffLen = strlen((const char *)buff); + if (buffLen > 0 && buff[buffLen - 1] == '\n') { + buff[buffLen - 1] = 0; + } // @warning: leak! but we don't care result.error_message = Format(GetSystemAllocator(), "Failed to create process | message: %s | cmd: %.*s", (char *)lpMsgBuf, FmtString(args)); + LocalFree(lpMsgBuf); } return result; } diff --git a/src/transcript_browser/config.cpp b/src/transcript_browser/config.cpp new file mode 100644 index 0000000..6a94be0 --- /dev/null +++ b/src/transcript_browser/config.cpp @@ -0,0 +1,153 @@ +struct Var { + String key; + String value; +}; + +struct ConfigParseResult { + Array vars; + Array errors; +}; + +char SRTCommand[512] = "\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" --start-time {time_in_seconds} {video}"; +char PDFCommand[512] = "C:/Users/Karol/AppData/Local/SumatraPDF/SumatraPDF.exe -page {page} {file}"; +char TXTCommand[512] = "notepad.exe {file}"; + +void SkipWhitespace(String *s) { + while (s->len && IsWhitespace(s->data[0])) { + *s = s->skip(1); + } +} + +String ParseWord(String *s) { + String result = {}; + SkipWhitespace(s); + if (s->len && IsAlphabetic(s->data[0])) { + result = {s->data, 0}; + while (s->len && IsAlphanumeric(s->data[0])) { + result.len += 1; + *s = s->skip(1); + } + } + return result; +} + +bool ExpectString(String *s, String expect) { + SkipWhitespace(s); + if (StartsWith(*s, expect)) { + *s = s->skip(expect.len); + return true; + } + return false; +} + +ConfigParseResult ParseConfig(Allocator allocator, String string) { + Scratch scratch; + Array lines = Split(scratch, string, "\n"); + + ConfigParseResult result = {}; + result.errors = {allocator}; + result.vars = {allocator}; + + For(lines) { + String s = Trim(it); + if (s.len == 0) continue; + + String key = ParseWord(&s); + if (key.len == 0) { + result.errors.add(Format(allocator, "failed to parse config at line: %d, the key is invalid", (int)lines.get_index(it))); + continue; + } + bool e = ExpectString(&s, "="); + if (!e) { + result.errors.add(Format(allocator, "failed to parse config at line: %d, expected '=' assignment sign", (int)lines.get_index(it))); + continue; + } + String value = Trim(s); + if (value.len == 0) { + result.errors.add(Format(allocator, "failed to parse config at line: %d, the value is invalid", (int)lines.get_index(it))); + continue; + } + + result.vars.add({key, value}); + } + return result; +} + +String GetValue(Array vars, String key) { + For(vars) { + if (it.key == key) return it.value; + } + return {}; +} + +String SerializeConfig(Allocator allocator) { + String content = Format(allocator, "SRTCommand = %s\nPDFCommand = %s\nTXTCommand = %s\n", SRTCommand, PDFCommand, TXTCommand); + return content; +} + +String CreateDefaultConfig(String path) { + String content = SerializeConfig(Perm); + WriteFile(path, content); + return content; +} + +void LoadConfig() { + Scratch scratch; + String exe_dir = GetExeDir(scratch); + String config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir)); + String config = ReadFile(Perm, config_path); + if (config.len == 0) { + config = CreateDefaultConfig(config_path); + } + + ConfigParseResult result = ParseConfig(Perm, config); + For(result.errors) { + XFileLoadResults.add({"", it}); + } + + String pdf_command = GetValue(result.vars, "PDFCommand"); + if (pdf_command.len) { + int len = ClampTop((int)pdf_command.len, (int)sizeof(PDFCommand) - 1); + memcpy(PDFCommand, pdf_command.data, len); + PDFCommand[len] = 0; + } + String txt_command = GetValue(result.vars, "TXTCommand"); + if (txt_command.len) { + int len = ClampTop((int)txt_command.len, (int)sizeof(TXTCommand) - 1); + memcpy(TXTCommand, txt_command.data, len); + TXTCommand[len] = 0; + } + String srt_command = GetValue(result.vars, "SRTCommand"); + if (srt_command.len) { + int len = ClampTop((int)srt_command.len, (int)sizeof(SRTCommand) - 1); + memcpy(SRTCommand, srt_command.data, len); + SRTCommand[len] = 0; + } +} + +void SaveConfig() { + Scratch scratch; + String exe_dir = GetExeDir(scratch); + String config_path = Format(scratch, "%.*s/transcript_browser.config", FmtString(exe_dir)); + String content = SerializeConfig(scratch); + WriteFile(config_path, content); +} + +void TestConfig() { + Scratch scratch; + { + ConfigParseResult result = ParseConfig(scratch, "\nPDFCommand = CoolCommand\nTXTCommand = ABC\nSRTCommand = DAS\n"); + Assert(result.errors.len == 0); + Assert(result.vars.len == 3); + Assert(result.vars[0].key == "PDFCommand"); + Assert(result.vars[1].value == "ABC"); + Assert(result.vars[2].value == "DAS"); + Assert(result.vars[2].key == "SRTCommand"); + } + { + ConfigParseResult result = ParseConfig(scratch, "\n= CoolCommand\nTXTCommand = ABC\nSRTCommand =\n"); + Assert(result.errors.len == 2); + Assert(result.vars.len == 1); + } + LoadConfig(); +} \ No newline at end of file diff --git a/src/transcript_browser/loading_thread.cpp b/src/transcript_browser/loading_thread.cpp index 1856947..671358a 100644 --- a/src/transcript_browser/loading_thread.cpp +++ b/src/transcript_browser/loading_thread.cpp @@ -28,16 +28,10 @@ struct XToSource { }; }; -struct FileLoadResult { - String filename; - String error; -}; - -Arena XArena; -std::mutex XArenaAddMutex; -Array XToSourceArray; -Array XFileLoadResults; -int64_t XLoadThreadComplete; +Arena XArena; +std::mutex XArenaAddMutex; +Array XToSourceArray; +int64_t XLoadThreadComplete; void XInitLoading() { InitArena(&XArena); @@ -68,8 +62,8 @@ WORK_FUNCTION(ParseFilesWork) { } else if (EndsWith(it_time_file, ".txt", true) || EndsWith(it_time_file, ".html", true)) { String string = ReadFile(scratch, it_time_file); if (string.data) { - XArenaAddMutex.lock(); Array lines = Split(scratch, string, "\n"); + XArenaAddMutex.lock(); For(lines) { String s = Copy(XArena, it); s.data[s.len] = ' '; @@ -83,6 +77,19 @@ WORK_FUNCTION(ParseFilesWork) { } else { XFileLoadResults.bounded_add({it_time_file, "failed to read the file"}); } + } else if (EndsWith(it_time_file, ".pdf", true)) { + PDF pdf = pdfioReadPDF(scratch, it_time_file); + + XArenaAddMutex.lock(); + For(pdf.pages) { + String s = Copy(XArena, it.string); + s.data[s.len] = ' '; + + XToSource t = {SourceKind_PDF, s, it_time_file}; + t.pdf = {it.number}; + XToSourceArray.add(t); + } + XArenaAddMutex.unlock(); } else { XFileLoadResults.bounded_add({it_time_file, "internal error: extension is not supported but got propagated to parse stage"}); } @@ -114,6 +121,8 @@ void XAddFolder(String folder, Array *filenames) { files_to_parse.add(file); } else if (EndsWith(iter.filename, ".txt", true) || EndsWith(iter.filename, ".html", true)) { files_to_parse.add(file); + } else if (EndsWith(iter.filename, ".pdf", true)) { + files_to_parse.add(file); } } diff --git a/src/transcript_browser/main.cpp b/src/transcript_browser/main.cpp index 76c475f..53412fc 100644 --- a/src/transcript_browser/main.cpp +++ b/src/transcript_browser/main.cpp @@ -1,5 +1,5 @@ /* -@todo: txt files +@todo: popup when error @todo: read recursive @todo: add option for user to control what files are read @todo: pdf files @@ -25,20 +25,24 @@ #include #include "glad.h" +struct FileLoadResult { + String filename; + String error; +}; + +Arena Perm; +Array XFileLoadResults; + +#include "config.cpp" #include #include "read_pdf.cpp" #include "read_srt.cpp" -Arena Perm; WorkQueue MainWorkQueue; #include "loading_thread.cpp" #include "searching_thread.cpp" -char SRTCommand[512] = "\"C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\" --start-time {time_in_seconds} {video}"; -char PDFCommand[512]; -char TXTCommand[512] = "subl.exe {file}"; - struct ReplaceVar { String replace; String with; @@ -160,6 +164,14 @@ void UISearchResults(Array filenames) { String args = ReplaceVars(scratch, replace_vars, TXTCommand); Process process = RunEx(args); if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); + } else if (item->kind == SourceKind_PDF) { + replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))}); + replace_vars.add({"page", Format(scratch, "%d", item->pdf.page)}); + String args = ReplaceVars(scratch, replace_vars, PDFCommand); + Process process = RunEx(args); + if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); + } else { + Assert(!"Invalid codepath"); } } ImGui::SameLine(); @@ -395,6 +407,9 @@ int main(int, char **) { ImGui::InputText(".srt", SRTCommand, sizeof(SRTCommand)); ImGui::InputText(".pdf", PDFCommand, sizeof(PDFCommand)); ImGui::InputText(".txt", TXTCommand, sizeof(TXTCommand)); + if (ImGui::Button("Save config")) { + SaveConfig(); + } } else { if (show_loaded_files) { UILoadedFiles(); diff --git a/src/transcript_browser/user_help.txt b/src/transcript_browser/user_help.txt new file mode 100644 index 0000000..c7f782a --- /dev/null +++ b/src/transcript_browser/user_help.txt @@ -0,0 +1,4 @@ +When you are refering to a particular program through an absolute path, for example: +> C:/Program files/SumatraPDF/SumatraPDF.exe +If the path has spaces, make sure to sandvich the path with quotes '"', like this: +> "C:/Program files/SumatraPDF/SumatraPDF.exe" \ No newline at end of file