TextSearch: way speeder by using xargs + grep
* Previously, each file was starting a shell to run grep command on it. Very suboptimal performance, big overhead. * Now a thread write each file path to xargs input, while another start xargs, let it distribute files on grep processes (one per cpu) and collect results asap. * This bring results way faster than previously. * Rename Escape Text setting into Regular Expression, as name was more after shell workaround than after function. * While it doesn't use a native text searching, by reusing both grep and xargs power, it answer the main issue with #9529.
This commit is contained in:
parent
2d27c2d003
commit
33d4c8a62e
@ -90,7 +90,7 @@ GrepWindow::GrepWindow(BMessage* message)
|
|||||||
fRecurseDirs(NULL),
|
fRecurseDirs(NULL),
|
||||||
fSkipDotDirs(NULL),
|
fSkipDotDirs(NULL),
|
||||||
fCaseSensitive(NULL),
|
fCaseSensitive(NULL),
|
||||||
fEscapeText(NULL),
|
fRegularExpression(NULL),
|
||||||
fTextOnly(NULL),
|
fTextOnly(NULL),
|
||||||
fInvokePe(NULL),
|
fInvokePe(NULL),
|
||||||
fHistoryMenu(NULL),
|
fHistoryMenu(NULL),
|
||||||
@ -211,8 +211,8 @@ void GrepWindow::MessageReceived(BMessage* message)
|
|||||||
_OnCaseSensitive();
|
_OnCaseSensitive();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_ESCAPE_TEXT:
|
case MSG_REGULAR_EXPRESSION:
|
||||||
_OnEscapeText();
|
_OnRegularExpression();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_TEXT_ONLY:
|
case MSG_TEXT_ONLY:
|
||||||
@ -452,8 +452,8 @@ GrepWindow::_CreateMenus()
|
|||||||
fCaseSensitive = new BMenuItem(
|
fCaseSensitive = new BMenuItem(
|
||||||
B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));
|
B_TRANSLATE("Case-sensitive"), new BMessage(MSG_CASE_SENSITIVE));
|
||||||
|
|
||||||
fEscapeText = new BMenuItem(
|
fRegularExpression = new BMenuItem(
|
||||||
B_TRANSLATE("Escape search text"), new BMessage(MSG_ESCAPE_TEXT));
|
B_TRANSLATE("Regular expression"), new BMessage(MSG_REGULAR_EXPRESSION));
|
||||||
|
|
||||||
fTextOnly = new BMenuItem(
|
fTextOnly = new BMenuItem(
|
||||||
B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));
|
B_TRANSLATE("Text files only"), new BMessage(MSG_TEXT_ONLY));
|
||||||
@ -486,7 +486,7 @@ GrepWindow::_CreateMenus()
|
|||||||
fPreferencesMenu->AddItem(fRecurseDirs);
|
fPreferencesMenu->AddItem(fRecurseDirs);
|
||||||
fPreferencesMenu->AddItem(fSkipDotDirs);
|
fPreferencesMenu->AddItem(fSkipDotDirs);
|
||||||
fPreferencesMenu->AddItem(fCaseSensitive);
|
fPreferencesMenu->AddItem(fCaseSensitive);
|
||||||
fPreferencesMenu->AddItem(fEscapeText);
|
fPreferencesMenu->AddItem(fRegularExpression);
|
||||||
fPreferencesMenu->AddItem(fTextOnly);
|
fPreferencesMenu->AddItem(fTextOnly);
|
||||||
fPreferencesMenu->AddItem(fInvokePe);
|
fPreferencesMenu->AddItem(fInvokePe);
|
||||||
|
|
||||||
@ -607,7 +607,7 @@ GrepWindow::_LoadPrefs()
|
|||||||
fRecurseLinks->SetMarked(fModel->fRecurseLinks);
|
fRecurseLinks->SetMarked(fModel->fRecurseLinks);
|
||||||
fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
|
fSkipDotDirs->SetMarked(fModel->fSkipDotDirs);
|
||||||
fCaseSensitive->SetMarked(fModel->fCaseSensitive);
|
fCaseSensitive->SetMarked(fModel->fCaseSensitive);
|
||||||
fEscapeText->SetMarked(fModel->fEscapeText);
|
fRegularExpression->SetMarked(fModel->fRegularExpression);
|
||||||
fTextOnly->SetMarked(fModel->fTextOnly);
|
fTextOnly->SetMarked(fModel->fTextOnly);
|
||||||
fInvokePe->SetMarked(fModel->fInvokePe);
|
fInvokePe->SetMarked(fModel->fInvokePe);
|
||||||
|
|
||||||
@ -865,12 +865,14 @@ GrepWindow::_OnNodeMonitorEvent(BMessage* message)
|
|||||||
if (entry.GetRef(&ref) == B_OK) {
|
if (entry.GetRef(&ref) == B_OK) {
|
||||||
int32 index;
|
int32 index;
|
||||||
ResultItem* item = fSearchResults->FindItem(ref, &index);
|
ResultItem* item = fSearchResults->FindItem(ref, &index);
|
||||||
item->SetText(path.String());
|
if (item) {
|
||||||
// take care of invalidation, the index is currently
|
item->SetText(path.String());
|
||||||
// the full list index, but needs to be the visible
|
// take care of invalidation, the index is currently
|
||||||
// items index for this
|
// the full list index, but needs to be the visible
|
||||||
index = fSearchResults->IndexOf(item);
|
// items index for this
|
||||||
fSearchResults->InvalidateItem(index);
|
index = fSearchResults->IndexOf(item);
|
||||||
|
fSearchResults->InvalidateItem(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -969,7 +971,6 @@ void
|
|||||||
GrepWindow::_OnReportResult(BMessage* message)
|
GrepWindow::_OnReportResult(BMessage* message)
|
||||||
{
|
{
|
||||||
CALLED();
|
CALLED();
|
||||||
|
|
||||||
entry_ref ref;
|
entry_ref ref;
|
||||||
if (message->FindRef("ref", &ref) != B_OK)
|
if (message->FindRef("ref", &ref) != B_OK)
|
||||||
return;
|
return;
|
||||||
@ -1056,10 +1057,10 @@ GrepWindow::_OnSkipDotDirs()
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
GrepWindow::_OnEscapeText()
|
GrepWindow::_OnRegularExpression()
|
||||||
{
|
{
|
||||||
fModel->fEscapeText = !fModel->fEscapeText;
|
fModel->fRegularExpression = !fModel->fRegularExpression;
|
||||||
fEscapeText->SetMarked(fModel->fEscapeText);
|
fRegularExpression->SetMarked(fModel->fRegularExpression);
|
||||||
_ModelChanged();
|
_ModelChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ private:
|
|||||||
void _OnRecurseLinks();
|
void _OnRecurseLinks();
|
||||||
void _OnRecurseDirs();
|
void _OnRecurseDirs();
|
||||||
void _OnSkipDotDirs();
|
void _OnSkipDotDirs();
|
||||||
void _OnEscapeText();
|
void _OnRegularExpression();
|
||||||
void _OnCaseSensitive();
|
void _OnCaseSensitive();
|
||||||
void _OnTextOnly();
|
void _OnTextOnly();
|
||||||
void _OnInvokePe();
|
void _OnInvokePe();
|
||||||
@ -103,7 +103,7 @@ private:
|
|||||||
BMenuItem* fRecurseDirs;
|
BMenuItem* fRecurseDirs;
|
||||||
BMenuItem* fSkipDotDirs;
|
BMenuItem* fSkipDotDirs;
|
||||||
BMenuItem* fCaseSensitive;
|
BMenuItem* fCaseSensitive;
|
||||||
BMenuItem* fEscapeText;
|
BMenuItem* fRegularExpression;
|
||||||
BMenuItem* fTextOnly;
|
BMenuItem* fTextOnly;
|
||||||
BMenuItem* fInvokePe;
|
BMenuItem* fInvokePe;
|
||||||
BMenu* fHistoryMenu;
|
BMenu* fHistoryMenu;
|
||||||
|
@ -10,6 +10,11 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <OS.h>
|
||||||
|
#include <image.h>
|
||||||
|
|
||||||
#include <Catalog.h>
|
#include <Catalog.h>
|
||||||
#include <Directory.h>
|
#include <Directory.h>
|
||||||
@ -26,14 +31,11 @@
|
|||||||
#define B_TRANSLATION_CONTEXT "Grepper"
|
#define B_TRANSLATION_CONTEXT "Grepper"
|
||||||
|
|
||||||
|
|
||||||
|
const char* kEOFTag = "//EOF";
|
||||||
|
|
||||||
|
|
||||||
using std::nothrow;
|
using std::nothrow;
|
||||||
|
|
||||||
// TODO: stippi: Check if this is a the best place to maintain a global
|
|
||||||
// list of files and folders for node monitoring. It should probably monitor
|
|
||||||
// every file that was grepped, as well as every visited (sub) folder.
|
|
||||||
// For the moment I don't know the life cycle of the Grepper object.
|
|
||||||
|
|
||||||
|
|
||||||
char*
|
char*
|
||||||
strdup_to_utf8(uint32 encode, const char* src, int32 length)
|
strdup_to_utf8(uint32 encode, const char* src, int32 length)
|
||||||
{
|
{
|
||||||
@ -85,12 +87,13 @@ Grepper::Grepper(const char* pattern, const Model* model,
|
|||||||
const BHandler* target, FileIterator* iterator)
|
const BHandler* target, FileIterator* iterator)
|
||||||
: fPattern(NULL),
|
: fPattern(NULL),
|
||||||
fTarget(target),
|
fTarget(target),
|
||||||
fEscapeText(model->fEscapeText),
|
fRegularExpression(model->fRegularExpression),
|
||||||
fCaseSensitive(model->fCaseSensitive),
|
fCaseSensitive(model->fCaseSensitive),
|
||||||
fEncoding(model->fEncoding),
|
fEncoding(model->fEncoding),
|
||||||
|
|
||||||
fIterator(iterator),
|
fIterator(iterator),
|
||||||
fThreadId(-1),
|
fRunnerThreadId(-1),
|
||||||
|
fXargsInput(-1),
|
||||||
fMustQuit(false)
|
fMustQuit(false)
|
||||||
{
|
{
|
||||||
if (fEncoding > 0) {
|
if (fEncoding > 0) {
|
||||||
@ -125,23 +128,23 @@ Grepper::Start()
|
|||||||
Cancel();
|
Cancel();
|
||||||
|
|
||||||
fMustQuit = false;
|
fMustQuit = false;
|
||||||
fThreadId = spawn_thread(
|
fRunnerThreadId = spawn_thread(
|
||||||
_SpawnThread, "_GrepperThread", B_NORMAL_PRIORITY, this);
|
_SpawnRunnerThread, "Grep runner", B_NORMAL_PRIORITY, this);
|
||||||
|
|
||||||
resume_thread(fThreadId);
|
resume_thread(fRunnerThreadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Grepper::Cancel()
|
Grepper::Cancel()
|
||||||
{
|
{
|
||||||
if (fThreadId < 0)
|
if (fRunnerThreadId < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fMustQuit = true;
|
fMustQuit = true;
|
||||||
int32 exitValue;
|
int32 exitValue;
|
||||||
wait_for_thread(fThreadId, &exitValue);
|
wait_for_thread(fRunnerThreadId, &exitValue);
|
||||||
fThreadId = -1;
|
fRunnerThreadId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -149,52 +152,45 @@ Grepper::Cancel()
|
|||||||
|
|
||||||
|
|
||||||
int32
|
int32
|
||||||
Grepper::_SpawnThread(void* cookie)
|
Grepper::_SpawnWriterThread(void* cookie)
|
||||||
{
|
{
|
||||||
Grepper* self = static_cast<Grepper*>(cookie);
|
Grepper* self = static_cast<Grepper*>(cookie);
|
||||||
return self->_GrepperThread();
|
return self->_WriterThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int32
|
int32
|
||||||
Grepper::_GrepperThread()
|
Grepper::_WriterThread()
|
||||||
{
|
{
|
||||||
BMessage message;
|
BMessage message;
|
||||||
|
char fileName[B_PATH_NAME_LENGTH*2];
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
char fileName[B_PATH_NAME_LENGTH];
|
printf("paths_writer started.\n");
|
||||||
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)) {
|
while (!fMustQuit && fIterator->GetNextName(fileName)) {
|
||||||
|
|
||||||
message.MakeEmpty();
|
message.MakeEmpty();
|
||||||
message.what = MSG_REPORT_FILE_NAME;
|
message.what = MSG_REPORT_FILE_NAME;
|
||||||
message.AddString("filename", fileName);
|
message.AddString("filename", fileName);
|
||||||
fTarget.SendMessage(&message);
|
// fTarget.SendMessage(&message);
|
||||||
|
|
||||||
message.MakeEmpty();
|
|
||||||
message.what = MSG_REPORT_RESULT;
|
|
||||||
message.AddString("filename", fileName);
|
|
||||||
|
|
||||||
BEntry entry(fileName);
|
BEntry entry(fileName);
|
||||||
entry_ref ref;
|
entry_ref ref;
|
||||||
entry.GetRef(&ref);
|
entry.GetRef(&ref);
|
||||||
message.AddRef("ref", &ref);
|
|
||||||
|
|
||||||
if (!entry.Exists()) {
|
if (!entry.Exists()) {
|
||||||
if (fIterator->NotifyNegatives())
|
if (fIterator->NotifyNegatives()) {
|
||||||
|
message.what = MSG_REPORT_RESULT;
|
||||||
|
message.AddRef("ref", &ref);
|
||||||
fTarget.SendMessage(&message);
|
fTarget.SendMessage(&message);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_EscapeSpecialChars(fileName, B_PATH_NAME_LENGTH)) {
|
if (!_EscapeSpecialChars(fileName, B_PATH_NAME_LENGTH)) {
|
||||||
|
char tempString[B_PATH_NAME_LENGTH + 32];
|
||||||
sprintf(tempString, B_TRANSLATE("%s: Not enough room to escape "
|
sprintf(tempString, B_TRANSLATE("%s: Not enough room to escape "
|
||||||
"the filename."), fileName);
|
"the filename."), fileName);
|
||||||
|
|
||||||
message.MakeEmpty();
|
message.MakeEmpty();
|
||||||
message.what = MSG_REPORT_ERROR;
|
message.what = MSG_REPORT_ERROR;
|
||||||
message.AddString("error", tempString);
|
message.AddString("error", tempString);
|
||||||
@ -202,47 +198,232 @@ Grepper::_GrepperThread()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(command, "grep -hn %s %s \"%s\" > \"%s\"",
|
// file exists, send it to xargs
|
||||||
fCaseSensitive ? "" : "-i", fPattern, fileName, tempFile.Path());
|
write(fXargsInput, fileName, strlen(fileName));
|
||||||
|
write(fXargsInput, "\n", 1);
|
||||||
|
// printf(">>>>>> %s\n", fileName);
|
||||||
|
|
||||||
int res = system(command);
|
fTarget.SendMessage(&message);
|
||||||
|
|
||||||
if (res == 0 || res == 1) {
|
count++;
|
||||||
FILE *results = fopen(tempFile.Path(), "r");
|
}
|
||||||
|
|
||||||
if (results != NULL) {
|
write(fXargsInput, kEOFTag, strlen(kEOFTag));
|
||||||
while (fgets(tempString, B_PATH_NAME_LENGTH, results) != 0) {
|
write(fXargsInput, "\n", 1);
|
||||||
if (fEncoding > 0) {
|
close(fXargsInput);
|
||||||
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())
|
printf("paths_writer stopped (%d paths).\n", count);
|
||||||
fTarget.SendMessage(&message);
|
|
||||||
|
|
||||||
fclose(results);
|
return 0;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(tempString, B_TRANSLATE("%s: There was a problem running grep."), fileName);
|
|
||||||
|
|
||||||
|
int32
|
||||||
|
Grepper::_SpawnRunnerThread(void* cookie)
|
||||||
|
{
|
||||||
|
Grepper* self = static_cast<Grepper*>(cookie);
|
||||||
|
return self->_RunnerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32
|
||||||
|
Grepper::_RunnerThread()
|
||||||
|
{
|
||||||
|
BMessage message;
|
||||||
|
char fileName[B_PATH_NAME_LENGTH];
|
||||||
|
|
||||||
|
const char* argv[32];
|
||||||
|
int argc = 0;
|
||||||
|
argv[argc++] = "xargs";
|
||||||
|
|
||||||
|
// can't use yet the --null mode due to pipe issue
|
||||||
|
// the xargs stdin input pipe closure is not detected
|
||||||
|
// by xargs. Instead, we use eof-string mode
|
||||||
|
|
||||||
|
// argv[argc++] = "--null";
|
||||||
|
argv[argc++] = "-E";
|
||||||
|
argv[argc++] = kEOFTag;
|
||||||
|
|
||||||
|
// Enable parallel mode
|
||||||
|
// Retrieve cpu count for to parallel xargs via -P argument
|
||||||
|
char cpuCount[8];
|
||||||
|
system_info sys_info;
|
||||||
|
get_system_info(&sys_info);
|
||||||
|
snprintf(cpuCount, sizeof(cpuCount), "%d", sys_info.cpu_count);
|
||||||
|
argv[argc++] = "-P";
|
||||||
|
argv[argc++] = cpuCount;
|
||||||
|
|
||||||
|
// grep command driven by xargs dispatcher
|
||||||
|
argv[argc++] = "grep";
|
||||||
|
argv[argc++] = "-n"; // need matching line(s) number(s)
|
||||||
|
argv[argc++] = "-H"; // need filename prefix
|
||||||
|
if (! fCaseSensitive)
|
||||||
|
argv[argc++] = "-i";
|
||||||
|
if (! fRegularExpression)
|
||||||
|
argv[argc++] = "-F"; // no a regexp: force fixed string,
|
||||||
|
argv[argc++] = fPattern;
|
||||||
|
argv[argc] = NULL;
|
||||||
|
|
||||||
|
// prepare xargs to run with stdin, stdout and stderr pipes
|
||||||
|
|
||||||
|
int oldStdIn, oldStdOut, oldStdErr;
|
||||||
|
oldStdIn = dup(STDIN_FILENO);
|
||||||
|
oldStdOut = dup(STDOUT_FILENO);
|
||||||
|
oldStdErr = dup(STDERR_FILENO);
|
||||||
|
|
||||||
|
int fds[2];
|
||||||
|
pipe(fds); dup2(fds[0], STDIN_FILENO); close(fds[0]);
|
||||||
|
fXargsInput = fds[1]; // write to in, appears on command's stdin
|
||||||
|
|
||||||
|
pipe(fds); dup2(fds[1], STDOUT_FILENO); close(fds[1]);
|
||||||
|
int out = fds[0]; // read from out, taken from command's stdout
|
||||||
|
|
||||||
|
pipe(fds);dup2(fds[1], STDERR_FILENO); close(fds[1]);
|
||||||
|
int err = fds[0]; // read from err, taken from command's stderr
|
||||||
|
|
||||||
|
// "load" command
|
||||||
|
thread_id xargsThread = load_image(argc, argv,
|
||||||
|
const_cast<const char**>(environ));
|
||||||
|
// xargsThread is suspended after loading
|
||||||
|
|
||||||
|
// restore our previous stdin, stdout and stderr
|
||||||
|
close(STDIN_FILENO); dup(oldStdIn); close(oldStdIn);
|
||||||
|
close(STDOUT_FILENO); dup(oldStdOut); close(oldStdOut);
|
||||||
|
close(STDERR_FILENO); dup(oldStdErr); close(oldStdErr);
|
||||||
|
|
||||||
|
if (xargsThread < B_OK) {
|
||||||
message.MakeEmpty();
|
message.MakeEmpty();
|
||||||
message.what = MSG_REPORT_ERROR;
|
message.what = MSG_REPORT_ERROR;
|
||||||
message.AddString("error", tempString);
|
message.AddString("error",
|
||||||
|
B_TRANSLATE("Failed to start xargs program!"));
|
||||||
fTarget.SendMessage(&message);
|
fTarget.SendMessage(&message);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
set_thread_priority(xargsThread, B_LOW_PRIORITY);
|
||||||
|
|
||||||
|
thread_id writerThread = spawn_thread(_SpawnWriterThread,
|
||||||
|
"Grep writer", B_LOW_PRIORITY, this);
|
||||||
|
// let's go!
|
||||||
|
resume_thread(xargsThread);
|
||||||
|
resume_thread(writerThread);
|
||||||
|
|
||||||
|
// Listen on xargs's stdout and stderr via select()
|
||||||
|
printf("Running: ");
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
printf("%s ", argv[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
int fdl[2] = { out, err };
|
||||||
|
int maxfd = 0;
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (maxfd < fdl[i])
|
||||||
|
maxfd = fdl[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_set readSet;
|
||||||
|
char line[B_PATH_NAME_LENGTH * 2];
|
||||||
|
|
||||||
|
FILE* output = fdopen(out, "r");
|
||||||
|
FILE* errors = fdopen(err, "r");
|
||||||
|
|
||||||
|
char currentFileName[B_PATH_NAME_LENGTH];
|
||||||
|
currentFileName[0] = '\0';
|
||||||
|
bool canReadOutput, canReadErrors;
|
||||||
|
canReadOutput = canReadErrors = true;
|
||||||
|
while (!fMustQuit && (canReadOutput || canReadErrors)) {
|
||||||
|
FD_ZERO(&readSet);
|
||||||
|
if (canReadOutput) {
|
||||||
|
FD_SET(out, &readSet);
|
||||||
|
}
|
||||||
|
if (canReadErrors) {
|
||||||
|
FD_SET(err, &readSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = select(maxfd + 1, &readSet, NULL, NULL, NULL);
|
||||||
|
if (result < 0) {
|
||||||
|
printf("select(): %d (%s)\n", result, strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canReadOutput && FD_ISSET(out, &readSet)) {
|
||||||
|
if (fgets(line, sizeof(line), output) != NULL) {
|
||||||
|
// parse grep output
|
||||||
|
int lineNumber = -1;
|
||||||
|
int textPos = -1;
|
||||||
|
sscanf(line, "%[^\n:]:%d:%n", fileName, &lineNumber, &textPos);
|
||||||
|
// printf("sscanf(\"%s\") -> %s %d %d\n", line, fileName,
|
||||||
|
// lineNumber, textPos);
|
||||||
|
if (textPos > 0) {
|
||||||
|
if (strcmp(fileName, currentFileName) != 0) {
|
||||||
|
fTarget.SendMessage(&message);
|
||||||
|
|
||||||
|
strncpy(currentFileName, fileName,
|
||||||
|
sizeof(currentFileName));
|
||||||
|
|
||||||
|
message.MakeEmpty();
|
||||||
|
message.what = MSG_REPORT_RESULT;
|
||||||
|
message.AddString("filename", fileName);
|
||||||
|
|
||||||
|
BEntry entry(fileName);
|
||||||
|
entry_ref ref;
|
||||||
|
entry.GetRef(&ref);
|
||||||
|
message.AddRef("ref", &ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* text = &line[strlen(fileName)+1];
|
||||||
|
printf("[%s] %s", fileName, text);
|
||||||
|
if (fEncoding > 0) {
|
||||||
|
char* tempdup = strdup_to_utf8(fEncoding, text,
|
||||||
|
strlen(text));
|
||||||
|
message.AddString("text", tempdup);
|
||||||
|
free(tempdup);
|
||||||
|
} else {
|
||||||
|
message.AddString("text", text);
|
||||||
|
}
|
||||||
|
message.AddInt32("line", lineNumber);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
canReadOutput = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canReadErrors && FD_ISSET(err, &readSet)) {
|
||||||
|
if (fgets(line, sizeof(line), errors) != NULL) {
|
||||||
|
|
||||||
|
if (message.HasString("text"))
|
||||||
|
fTarget.SendMessage(&message);
|
||||||
|
currentFileName[0] = '\0';
|
||||||
|
|
||||||
|
message.MakeEmpty();
|
||||||
|
message.what = MSG_REPORT_ERROR;
|
||||||
|
message.AddString("error", line);
|
||||||
|
fTarget.SendMessage(&message);
|
||||||
|
} else {
|
||||||
|
canReadErrors = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send last pending message, if any
|
||||||
|
if (message.HasString("text"))
|
||||||
|
fTarget.SendMessage(&message);
|
||||||
|
|
||||||
|
printf("Done.\n");
|
||||||
|
fclose(output);
|
||||||
|
fclose(errors);
|
||||||
|
|
||||||
|
close(out);
|
||||||
|
close(err);
|
||||||
|
|
||||||
|
fMustQuit = true;
|
||||||
|
int32 exitValue;
|
||||||
|
wait_for_thread(xargsThread, &exitValue);
|
||||||
|
wait_for_thread(writerThread, &exitValue);
|
||||||
|
|
||||||
// We wait with removing the temporary file until after the
|
// We wait with removing the temporary file until after the
|
||||||
// entire search has finished, to prevent a lot of flickering
|
// entire search has finished, to prevent a lot of flickering
|
||||||
// if the Tracker window for /tmp/ might be open.
|
// if the Tracker window for /tmp/ might be open.
|
||||||
|
|
||||||
remove(tempFile.Path());
|
|
||||||
|
|
||||||
message.MakeEmpty();
|
message.MakeEmpty();
|
||||||
message.what = MSG_SEARCH_FINISHED;
|
message.what = MSG_SEARCH_FINISHED;
|
||||||
fTarget.SendMessage(&message);
|
fTarget.SendMessage(&message);
|
||||||
@ -257,52 +438,7 @@ Grepper::_SetPattern(const char* src)
|
|||||||
if (src == NULL)
|
if (src == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!fEscapeText) {
|
fPattern = strdup(src);
|
||||||
fPattern = strdup(src);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -314,7 +450,7 @@ Grepper::_EscapeSpecialChars(char* buffer, ssize_t bufferSize)
|
|||||||
uint32 len = strlen(copy);
|
uint32 len = strlen(copy);
|
||||||
bool result = true;
|
bool result = true;
|
||||||
for (uint32 count = 0; count < len; ++count) {
|
for (uint32 count = 0; count < len; ++count) {
|
||||||
if (copy[count] == '"' || copy[count] == '$')
|
if (copy[count] == ' ')
|
||||||
*buffer++ = '\\';
|
*buffer++ = '\\';
|
||||||
if (buffer - start == bufferSize - 1) {
|
if (buffer - start == bufferSize - 1) {
|
||||||
result = false;
|
result = false;
|
||||||
@ -326,4 +462,3 @@ Grepper::_EscapeSpecialChars(char* buffer, ssize_t bufferSize)
|
|||||||
free(copy);
|
free(copy);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,11 +24,13 @@ public:
|
|||||||
void Cancel();
|
void Cancel();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Spawns the real grepper thread.
|
// Spawns the real grepper threads.
|
||||||
static int32 _SpawnThread(void* cookie);
|
static int32 _SpawnRunnerThread(void* cookie);
|
||||||
|
static int32 _SpawnWriterThread(void* cookie);
|
||||||
|
|
||||||
// The thread function that does the actual grepping.
|
// The threads functions that does the actual grepping.
|
||||||
int32 _GrepperThread();
|
int32 _RunnerThread();
|
||||||
|
int32 _WriterThread();
|
||||||
|
|
||||||
// Remembers, and possibly escapes, the search pattern.
|
// Remembers, and possibly escapes, the search pattern.
|
||||||
void _SetPattern(const char* source);
|
void _SetPattern(const char* source);
|
||||||
@ -37,13 +39,14 @@ private:
|
|||||||
// to prevent the shell from misinterpreting them.
|
// to prevent the shell from misinterpreting them.
|
||||||
bool _EscapeSpecialChars(char* buffer,
|
bool _EscapeSpecialChars(char* buffer,
|
||||||
ssize_t bufferSize);
|
ssize_t bufferSize);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// The (escaped) search pattern.
|
// The (escaped) search pattern.
|
||||||
char* fPattern;
|
char* fPattern;
|
||||||
|
|
||||||
// The settings from the model.
|
// The settings from the model.
|
||||||
BMessenger fTarget;
|
BMessenger fTarget;
|
||||||
bool fEscapeText : 1;
|
bool fRegularExpression : 1;
|
||||||
bool fCaseSensitive : 1;
|
bool fCaseSensitive : 1;
|
||||||
uint32 fEncoding;
|
uint32 fEncoding;
|
||||||
|
|
||||||
@ -51,7 +54,9 @@ private:
|
|||||||
FileIterator* fIterator;
|
FileIterator* fIterator;
|
||||||
|
|
||||||
// Our thread's ID.
|
// Our thread's ID.
|
||||||
thread_id fThreadId;
|
thread_id fRunnerThreadId;
|
||||||
|
// xargs input pipe
|
||||||
|
int fXargsInput;
|
||||||
|
|
||||||
// Whether our thread must quit.
|
// Whether our thread must quit.
|
||||||
volatile bool fMustQuit;
|
volatile bool fMustQuit;
|
||||||
|
@ -35,7 +35,7 @@ Model::Model()
|
|||||||
fRecurseLinks(false),
|
fRecurseLinks(false),
|
||||||
fSkipDotDirs(true),
|
fSkipDotDirs(true),
|
||||||
fCaseSensitive(false),
|
fCaseSensitive(false),
|
||||||
fEscapeText(true),
|
fRegularExpression(false),
|
||||||
fTextOnly(true),
|
fTextOnly(true),
|
||||||
fInvokePe(false),
|
fInvokePe(false),
|
||||||
|
|
||||||
@ -91,8 +91,8 @@ Model::LoadPrefs()
|
|||||||
sizeof(int32)) > 0)
|
sizeof(int32)) > 0)
|
||||||
fCaseSensitive = (value != 0);
|
fCaseSensitive = (value != 0);
|
||||||
|
|
||||||
if (file.ReadAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
|
if (file.ReadAttr("RegularExpression", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
|
||||||
fEscapeText = (value != 0);
|
fRegularExpression = (value != 0);
|
||||||
|
|
||||||
if (file.ReadAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
|
if (file.ReadAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32)) > 0)
|
||||||
fTextOnly = (value != 0);
|
fTextOnly = (value != 0);
|
||||||
@ -147,8 +147,8 @@ Model::SavePrefs()
|
|||||||
value = fCaseSensitive ? 1 : 0;
|
value = fCaseSensitive ? 1 : 0;
|
||||||
file.WriteAttr("CaseSensitive", B_INT32_TYPE, 0, &value, sizeof(int32));
|
file.WriteAttr("CaseSensitive", B_INT32_TYPE, 0, &value, sizeof(int32));
|
||||||
|
|
||||||
value = fEscapeText ? 1 : 0;
|
value = fRegularExpression ? 1 : 0;
|
||||||
file.WriteAttr("EscapeText", B_INT32_TYPE, 0, &value, sizeof(int32));
|
file.WriteAttr("RegularExpression", B_INT32_TYPE, 0, &value, sizeof(int32));
|
||||||
|
|
||||||
value = fTextOnly ? 1 : 0;
|
value = fTextOnly ? 1 : 0;
|
||||||
file.WriteAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32));
|
file.WriteAttr("TextOnly", B_INT32_TYPE, 0, &value, sizeof(int32));
|
||||||
|
@ -23,7 +23,7 @@ enum {
|
|||||||
MSG_RECURSE_DIRS,
|
MSG_RECURSE_DIRS,
|
||||||
MSG_SKIP_DOT_DIRS,
|
MSG_SKIP_DOT_DIRS,
|
||||||
MSG_CASE_SENSITIVE,
|
MSG_CASE_SENSITIVE,
|
||||||
MSG_ESCAPE_TEXT,
|
MSG_REGULAR_EXPRESSION,
|
||||||
MSG_TEXT_ONLY,
|
MSG_TEXT_ONLY,
|
||||||
MSG_INVOKE_PE,
|
MSG_INVOKE_PE,
|
||||||
MSG_CHECKBOX_SHOW_LINES,
|
MSG_CHECKBOX_SHOW_LINES,
|
||||||
@ -87,8 +87,8 @@ public:
|
|||||||
// Whether the search is case sensitive.
|
// Whether the search is case sensitive.
|
||||||
bool fCaseSensitive;
|
bool fCaseSensitive;
|
||||||
|
|
||||||
// Whether the search pattern will be escaped.
|
// Whether the search pattern is a regular expression.
|
||||||
bool fEscapeText;
|
bool fRegularExpression;
|
||||||
|
|
||||||
// Whether we look at text files only.
|
// Whether we look at text files only.
|
||||||
bool fTextOnly;
|
bool fTextOnly;
|
||||||
|
Loading…
Reference in New Issue
Block a user