From f8f8d2bbece9d16213f5bab86584502bee94a22a Mon Sep 17 00:00:00 2001 From: Augustin Cavalier Date: Wed, 1 Apr 2015 16:23:42 -0400 Subject: [PATCH] Revert "TextSearch: use BString-based searching instead of Grep-based." This reverts commit 1e9f5f5be4c8419c2085f51c78c9ac02ef13377b. As per discussion on the ML. --- src/apps/text_search/GrepWindow.cpp | 32 +++++ src/apps/text_search/GrepWindow.h | 3 + src/apps/text_search/Grepper.cpp | 212 ++++++++++++++++++++-------- src/apps/text_search/Grepper.h | 19 +-- src/apps/text_search/Model.cpp | 7 + src/apps/text_search/Model.h | 5 + 6 files changed, 210 insertions(+), 68 deletions(-) diff --git a/src/apps/text_search/GrepWindow.cpp b/src/apps/text_search/GrepWindow.cpp index ee660209c4..5e2864e80a 100644 --- a/src/apps/text_search/GrepWindow.cpp +++ b/src/apps/text_search/GrepWindow.cpp @@ -90,6 +90,7 @@ GrepWindow::GrepWindow(BMessage* message) fRecurseDirs(NULL), fSkipDotDirs(NULL), fCaseSensitive(NULL), + fEscapeText(NULL), fTextOnly(NULL), fInvokePe(NULL), fHistoryMenu(NULL), @@ -210,6 +211,10 @@ void GrepWindow::MessageReceived(BMessage* message) _OnCaseSensitive(); break; + case MSG_ESCAPE_TEXT: + _OnEscapeText(); + break; + case MSG_TEXT_ONLY: _OnTextOnly(); break; @@ -254,6 +259,10 @@ void GrepWindow::MessageReceived(BMessage* message) _OnReportResult(message); break; + case MSG_REPORT_ERROR: + _OnReportError(message); + break; + case MSG_SELECT_ALL: _OnSelectAll(message); break; @@ -443,6 +452,9 @@ GrepWindow::_CreateMenus() fCaseSensitive = new BMenuItem( B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE)); + fEscapeText = new BMenuItem( + B_TRANSLATE("Escape search text"), new BMessage(MSG_ESCAPE_TEXT)); + fTextOnly = new BMenuItem( B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY)); @@ -474,6 +486,7 @@ GrepWindow::_CreateMenus() fPreferencesMenu->AddItem(fRecurseDirs); fPreferencesMenu->AddItem(fSkipDotDirs); fPreferencesMenu->AddItem(fCaseSensitive); + fPreferencesMenu->AddItem(fEscapeText); fPreferencesMenu->AddItem(fTextOnly); fPreferencesMenu->AddItem(fInvokePe); @@ -594,6 +607,7 @@ GrepWindow::_LoadPrefs() fRecurseLinks->SetMarked(fModel->fRecurseLinks); fSkipDotDirs->SetMarked(fModel->fSkipDotDirs); fCaseSensitive->SetMarked(fModel->fCaseSensitive); + fEscapeText->SetMarked(fModel->fEscapeText); fTextOnly->SetMarked(fModel->fTextOnly); fInvokePe->SetMarked(fModel->fInvokePe); @@ -1005,6 +1019,15 @@ GrepWindow::_OnReportResult(BMessage* message) } +void +GrepWindow::_OnReportError(BMessage* message) +{ + const char* buf; + if (message->FindString("error", &buf) == B_OK) + fSearchResults->AddItem(new BStringItem(buf)); +} + + void GrepWindow::_OnRecurseLinks() { @@ -1032,6 +1055,15 @@ GrepWindow::_OnSkipDotDirs() } +void +GrepWindow::_OnEscapeText() +{ + fModel->fEscapeText = !fModel->fEscapeText; + fEscapeText->SetMarked(fModel->fEscapeText); + _ModelChanged(); +} + + void GrepWindow::_OnCaseSensitive() { diff --git a/src/apps/text_search/GrepWindow.h b/src/apps/text_search/GrepWindow.h index 33a31c6be7..e65f9a65e6 100644 --- a/src/apps/text_search/GrepWindow.h +++ b/src/apps/text_search/GrepWindow.h @@ -50,9 +50,11 @@ private: void _OnNodeMonitorPulse(); void _OnReportFileName(BMessage* message); void _OnReportResult(BMessage* message); + void _OnReportError(BMessage* message); void _OnRecurseLinks(); void _OnRecurseDirs(); void _OnSkipDotDirs(); + void _OnEscapeText(); void _OnCaseSensitive(); void _OnTextOnly(); void _OnInvokePe(); @@ -101,6 +103,7 @@ private: BMenuItem* fRecurseDirs; BMenuItem* fSkipDotDirs; BMenuItem* fCaseSensitive; + BMenuItem* fEscapeText; BMenuItem* fTextOnly; BMenuItem* fInvokePe; BMenu* fHistoryMenu; diff --git a/src/apps/text_search/Grepper.cpp b/src/apps/text_search/Grepper.cpp index cf9a42f62a..1a40decb9a 100644 --- a/src/apps/text_search/Grepper.cpp +++ b/src/apps/text_search/Grepper.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include "FileIterator.h" @@ -60,10 +59,33 @@ strdup_to_utf8(uint32 encode, const char* src, int32 length) } -Grepper::Grepper(BString pattern, const Model* model, +char* +strdup_from_utf8(uint32 encode, const char* src, int32 length) +{ + int32 srcLen = length; + int32 dstLen = srcLen; + char* dst = new (nothrow) char[dstLen + 1]; + if (dst == NULL) + return NULL; + int32 cookie = 0; + convert_from_utf8(encode, src, &srcLen, dst, &dstLen, &cookie); + // TODO: See above. + dst[dstLen] = '\0'; + char* dup = strdup(dst); + delete[] dst; + if (srcLen != length) { + fprintf(stderr, "strdup_from_utf8(%" B_PRId32 ", %" B_PRId32 + ") dst allocate smalled(%" B_PRId32 ")\n", encode, length, dstLen); + } + return dup; +} + + +Grepper::Grepper(const char* pattern, const Model* model, const BHandler* target, FileIterator* iterator) - : fPattern(pattern), + : fPattern(NULL), fTarget(target), + fEscapeText(model->fEscapeText), fCaseSensitive(model->fCaseSensitive), fEncoding(model->fEncoding), @@ -71,12 +93,19 @@ Grepper::Grepper(BString pattern, const Model* model, fThreadId(-1), fMustQuit(false) { + if (fEncoding > 0) { + char* src = strdup_from_utf8(fEncoding, pattern, strlen(pattern)); + _SetPattern(src); + free(src); + } else + _SetPattern(pattern); } Grepper::~Grepper() { Cancel(); + free(fPattern); delete fIterator; } @@ -131,9 +160,17 @@ int32 Grepper::_GrepperThread() { BMessage message; + char fileName[B_PATH_NAME_LENGTH]; + char tempString[B_PATH_NAME_LENGTH]; + char command[B_PATH_NAME_LENGTH + 32]; + + BPath tempFile; + sprintf(fileName, "/tmp/SearchText%" B_PRId32, fThreadId); + tempFile.SetTo(fileName); while (!fMustQuit && fIterator->GetNextName(fileName)) { + message.MakeEmpty(); message.what = MSG_REPORT_FILE_NAME; message.AddString("filename", fileName); @@ -154,44 +191,58 @@ Grepper::_GrepperThread() continue; } - BString contents; + if (!_EscapeSpecialChars(fileName, B_PATH_NAME_LENGTH)) { + sprintf(tempString, B_TRANSLATE("%s: Not enough room to escape " + "the filename."), fileName); - BFile file(&ref, B_READ_WRITE); - off_t size; - file.GetSize(&size); - file.Seek(0, SEEK_SET); - char* buffer = new char[size]; - file.Read(buffer, size); - if (fEncoding > 0) { - char* temp = strdup_to_utf8(fEncoding, buffer, size); - contents.SetTo(temp, size); - free(temp); - } else - contents.SetTo(buffer, size); - delete[] buffer; - - int32 index = 0, lines = 1; // First line is 1 not 0 - while (true) { - int32 newPos; - if (fCaseSensitive) - newPos = contents.FindFirst(fPattern, index); - else - newPos = contents.IFindFirst(fPattern, index); - if (newPos == B_ERROR) - break; - - lines += _CountLines(contents, index, newPos); - BString linenoAndLine; - linenoAndLine.SetToFormat("%" B_PRId32 ":%s", lines, _GetLine(contents, newPos).String()); - message.AddString("text", linenoAndLine); - - index = newPos + 1; + message.MakeEmpty(); + message.what = MSG_REPORT_ERROR; + message.AddString("error", tempString); + fTarget.SendMessage(&message); + continue; } - if (message.HasString("text") || fIterator->NotifyNegatives()) - fTarget.SendMessage(&message); + sprintf(command, "grep -hn %s %s \"%s\" > \"%s\"", + fCaseSensitive ? "" : "-i", fPattern, fileName, tempFile.Path()); + + int res = system(command); + + if (res == 0 || res == 1) { + FILE *results = fopen(tempFile.Path(), "r"); + + if (results != NULL) { + while (fgets(tempString, B_PATH_NAME_LENGTH, results) != 0) { + if (fEncoding > 0) { + char* tempdup = strdup_to_utf8(fEncoding, tempString, + strlen(tempString)); + message.AddString("text", tempdup); + free(tempdup); + } else + message.AddString("text", tempString); + } + + if (message.HasString("text") || fIterator->NotifyNegatives()) + fTarget.SendMessage(&message); + + fclose(results); + continue; + } + } + + sprintf(tempString, B_TRANSLATE("%s: There was a problem running grep."), fileName); + + message.MakeEmpty(); + message.what = MSG_REPORT_ERROR; + message.AddString("error", tempString); + fTarget.SendMessage(&message); } + // We wait with removing the temporary file until after the + // entire search has finished, to prevent a lot of flickering + // if the Tracker window for /tmp/ might be open. + + remove(tempFile.Path()); + message.MakeEmpty(); message.what = MSG_SEARCH_FINISHED; fTarget.SendMessage(&message); @@ -200,36 +251,79 @@ Grepper::_GrepperThread() } -int32 -Grepper::_CountLines(BString& str, int32 startPos, int32 endPos) +void +Grepper::_SetPattern(const char* src) { - int32 ret = 0; - for (int32 i = startPos; i < endPos; i++) { - if (str[i] == '\n') - ret++; + if (src == NULL) + return; + + if (!fEscapeText) { + fPattern = strdup(src); + return; } - return ret; + + // We will simply guess the size of the memory buffer + // that we need. This should always be large enough. + fPattern = (char*)malloc((strlen(src) + 1) * 3 * sizeof(char)); + if (fPattern == NULL) + return; + + const char* srcPtr = src; + char* dstPtr = fPattern; + + // Put double quotes around the pattern, so separate + // words are considered to be part of a single string. + *dstPtr++ = '"'; + + while (*srcPtr != '\0') { + char c = *srcPtr++; + + // Put a backslash in front of characters + // that should be escaped. + if ((c == '.') || (c == ',') + || (c == '[') || (c == ']') + || (c == '?') || (c == '*') + || (c == '+') || (c == '-') + || (c == ':') || (c == '^') + || (c == '"') || (c == '`')) { + *dstPtr++ = '\\'; + } else if ((c == '\\') || (c == '$')) { + // Some characters need to be escaped + // with *three* backslashes in a row. + *dstPtr++ = '\\'; + *dstPtr++ = '\\'; + *dstPtr++ = '\\'; + } + + // Note: we do not have to escape the + // { } ( ) < > and | characters. + + *dstPtr++ = c; + } + + *dstPtr++ = '"'; + *dstPtr = '\0'; } -BString -Grepper::_GetLine(BString& str, int32 pos) +bool +Grepper::_EscapeSpecialChars(char* buffer, ssize_t bufferSize) { - int32 startPos = 0; - int32 endPos = str.Length(); - for (int32 i = pos; i > 0; i--) { - if (str[i] == '\n') { - startPos = i + 1; + char* copy = strdup(buffer); + char* start = buffer; + uint32 len = strlen(copy); + bool result = true; + for (uint32 count = 0; count < len; ++count) { + if (copy[count] == '"' || copy[count] == '$') + *buffer++ = '\\'; + if (buffer - start == bufferSize - 1) { + result = false; break; } + *buffer++ = copy[count]; } - for (int32 i = pos; i < endPos; i++) { - if (str[i] == '\n') { - endPos = i; - break; - } - } - - BString ret; - return str.CopyInto(ret, startPos, endPos - startPos); + *buffer = '\0'; + free(copy); + return result; } + diff --git a/src/apps/text_search/Grepper.h b/src/apps/text_search/Grepper.h index ea90da619a..221bff61fd 100644 --- a/src/apps/text_search/Grepper.h +++ b/src/apps/text_search/Grepper.h @@ -6,7 +6,6 @@ #define GREPPER_H #include -#include class FileIterator; class Model; @@ -14,7 +13,7 @@ class Model; // Executes "grep" in a background thread. class Grepper { public: - Grepper(BString pattern, const Model* model, + Grepper(const char* pattern, const Model* model, const BHandler* target, FileIterator* iterator); virtual ~Grepper(); @@ -31,14 +30,16 @@ private: // The thread function that does the actual grepping. int32 _GrepperThread(); - // Counts the number of linebreaks from startPos to endPos. - int32 _CountLines(BString& str, int32 startPos, - int32 endPos); - // Gets the complete line that "pos" is on. - BString _GetLine(BString& str, int32 pos); + // Remembers, and possibly escapes, the search pattern. + void _SetPattern(const char* source); + + // Prepends all quotes, dollars and backslashes with at backslash + // to prevent the shell from misinterpreting them. + bool _EscapeSpecialChars(char* buffer, + ssize_t bufferSize); private: - // The search pattern. - BString fPattern; + // The (escaped) search pattern. + char* fPattern; // The settings from the model. BMessenger fTarget; diff --git a/src/apps/text_search/Model.cpp b/src/apps/text_search/Model.cpp index 4f001121c4..c76d65ae41 100644 --- a/src/apps/text_search/Model.cpp +++ b/src/apps/text_search/Model.cpp @@ -35,6 +35,7 @@ Model::Model() fRecurseLinks(false), fSkipDotDirs(true), fCaseSensitive(false), + fEscapeText(true), fTextOnly(true), fInvokePe(false), @@ -90,6 +91,9 @@ Model::LoadPrefs() sizeof(int32)) > 0) fCaseSensitive = (value != 0); + if (file.ReadAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0) + fEscapeText = (value != 0); + if (file.ReadAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0) fTextOnly = (value != 0); @@ -143,6 +147,9 @@ Model::SavePrefs() value = fCaseSensitive ? 1 : 0; file.WriteAttr("CaseSensitive", B_INT32_TYPE, 0, &value, sizeof(int32)); + value = fEscapeText ? 1 : 0; + file.WriteAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32)); + value = fTextOnly ? 1 : 0; file.WriteAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)); diff --git a/src/apps/text_search/Model.h b/src/apps/text_search/Model.h index 96e7dc6c0b..7c465e7588 100644 --- a/src/apps/text_search/Model.h +++ b/src/apps/text_search/Model.h @@ -23,6 +23,7 @@ enum { MSG_RECURSE_DIRS, MSG_SKIP_DOT_DIRS, MSG_CASE_SENSITIVE, + MSG_ESCAPE_TEXT, MSG_TEXT_ONLY, MSG_INVOKE_PE, MSG_CHECKBOX_SHOW_LINES, @@ -34,6 +35,7 @@ enum { MSG_REPORT_FILE_NAME, MSG_REPORT_RESULT, + MSG_REPORT_ERROR, MSG_SEARCH_FINISHED, MSG_NEW_WINDOW, @@ -85,6 +87,9 @@ public: // Whether the search is case sensitive. bool fCaseSensitive; + // Whether the search pattern will be escaped. + bool fEscapeText; + // Whether we look at text files only. bool fTextOnly;