Finished implementing and enabled node monitoring on the folders and files

of the current search. If new files match the pattern, the appear in the
results, or are removed if they don't match anymore. The results also
adapt to changes in the files.
Basically, I added another iterator that is also used to track changes when
node monitor events arrive. Only those changed files are grepped again after
a timeout of .5 seconds when no new node monitor events pour in.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26809 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus 2008-08-04 21:51:33 +00:00
parent 2a24bab833
commit 962a6c67e9
11 changed files with 343 additions and 51 deletions

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "ChangesIterator.h"
#include <new>
#include <stdio.h>
#include <string.h>
#include <Directory.h>
#include "Model.h"
using std::nothrow;
ChangesIterator::ChangesIterator(const Model* model)
: FileIterator(),
fPathMap(),
fIteratorIndex(0),
fRecurseDirs(model->fRecurseDirs),
fRecurseLinks(model->fRecurseLinks),
fSkipDotDirs(model->fSkipDotDirs),
fTextOnly(model->fTextOnly)
{
}
ChangesIterator::~ChangesIterator()
{
}
bool
ChangesIterator::IsValid() const
{
return fPathMap.InitCheck() == B_OK;
}
bool
ChangesIterator::GetNextName(char* buffer)
{
// TODO: inefficient
PathMap::Iterator iterator = fPathMap.GetIterator();
int32 index = 0;
while (index < fIteratorIndex && iterator.HasNext())
iterator.Next();
if (iterator.HasNext()) {
const PathMap::Entry& entry = iterator.Next();
sprintf(buffer, "%s", entry.key.GetString());
fIteratorIndex++;
return true;
}
return false;
}
bool
ChangesIterator::NotifyNegatives() const
{
return true;
}
// #pragma mark -
void
ChangesIterator::EntryAdded(const char* path)
{
HashString key(path);
if (fPathMap.ContainsKey(key))
return;
fPathMap.Put(key, ENTRY_ADDED);
}
void
ChangesIterator::EntryRemoved(const char* path)
{
HashString key(path);
if (fPathMap.ContainsKey(key)) {
uint32 mode = fPathMap.Get(key);
if (mode == ENTRY_ADDED)
fPathMap.Remove(key);
else if (mode != ENTRY_REMOVED)
fPathMap.Put(key, ENTRY_REMOVED);
} else
fPathMap.Put(key, ENTRY_REMOVED);
}
void
ChangesIterator::EntryChanged(const char* path)
{
HashString key(path);
if (fPathMap.ContainsKey(key) && fPathMap.Get(key) == ENTRY_ADDED)
return;
fPathMap.Put(key, ENTRY_CHANGED);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#ifndef CHANGES_ITERATOR_H
#define CHANGES_ITERATOR_H
#include <HashMap.h>
#include <HashString.h>
#include "FileIterator.h"
class BEntry;
class BDirectory;
class Model;
class ChangesIterator : public FileIterator {
public:
ChangesIterator(const Model* model);
virtual ~ChangesIterator();
virtual bool IsValid() const;
virtual bool GetNextName(char* buffer);
virtual bool NotifyNegatives() const;
public:
void EntryAdded(const char* path);
void EntryRemoved(const char* path);
void EntryChanged(const char* path);
private:
typedef HashMap<HashString, uint32> PathMap;
enum {
ENTRY_ADDED = 0,
ENTRY_REMOVED,
ENTRY_CHANGED
};
PathMap fPathMap;
int32 fIteratorIndex;
bool fRecurseDirs : 1;
bool fRecurseLinks : 1;
bool fSkipDotDirs : 1;
bool fTextOnly : 1;
};
#endif // CHANGES_ITERATOR_H

View File

@ -40,3 +40,21 @@ GrepListView::GrepListView()
B_WILL_DRAW | B_NAVIGABLE)
{
}
ResultItem*
GrepListView::FindItem(const entry_ref& ref, int32* _index) const
{
int32 count = FullListCountItems();
for (int32 i = 0; i < count; i++) {
ResultItem* item = dynamic_cast<ResultItem*>(FullListItemAt(i));
if (item == NULL)
continue;
if (item->ref == ref) {
*_index = i;
return item;
}
}
*_index = -1;
return NULL;
}

View File

@ -38,6 +38,9 @@ public:
class GrepListView : public BOutlineListView {
public:
GrepListView();
ResultItem* FindItem(const entry_ref& ref,
int32* _index) const;
};
#endif // GREP_LIST_VIEW_H

View File

@ -33,19 +33,23 @@
#include <AppFileInfo.h>
#include <Alert.h>
#include <Clipboard.h>
#include <MessageRunner.h>
#include <Path.h>
#include <PathMonitor.h>
#include <Roster.h>
#include <String.h>
#include <UTF8.h>
#include "FolderIterator.h"
#include "ChangesIterator.h"
#include "GlobalDefs.h"
#include "Grepper.h"
#include "InitialIterator.h"
#include "Translation.h"
using std::nothrow;
static const bigtime_t kChangesPulseInterval = 500000;
GrepWindow::GrepWindow(BMessage* message)
: BWindow(BRect(0, 0, 1, 1), NULL, B_DOCUMENT_WINDOW, 0),
@ -86,8 +90,10 @@ GrepWindow::GrepWindow(BMessage* message)
fGrepper(NULL),
fOldPattern(""),
fModel(new (nothrow) Model()),
fLastNodeMonitorEvent(system_time()),
fChangesIterator(NULL),
fChangesPulse(NULL),
fFilePanel(NULL)
{
@ -225,6 +231,10 @@ void GrepWindow::MessageReceived(BMessage *message)
_OnNodeMonitorEvent(message);
break;
case MSG_NODE_MONITOR_PULSE:
_OnNodeMonitorPulse();
break;
case MSG_REPORT_FILE_NAME:
_OnReportFileName(message);
break;
@ -672,6 +682,8 @@ GrepWindow::_SavePrefs()
void
GrepWindow::_StartNodeMonitoring()
{
_StopNodeMonitoring();
BMessenger messenger(this);
uint32 fileFlags = B_WATCH_NAME | B_WATCH_STAT;
uint32 dirFlags = B_WATCH_DIRECTORY | B_WATCH_NAME;
@ -679,11 +691,11 @@ GrepWindow::_StartNodeMonitoring()
// watch the top level folder
BPath path(&fModel->fDirectory);
if (path.InitCheck() == B_OK) {
printf("start monitoring root folder: %s\n", path.Path());
//printf("start monitoring root folder: %s\n", path.Path());
BPrivate::BPathMonitor::StartWatching(path.Path(), dirFlags, messenger);
}
FolderIterator iterator(fModel);
InitialIterator iterator(fModel);
BEntry entry;
while (iterator.GetTopEntry(entry)) {
@ -691,17 +703,23 @@ printf("start monitoring root folder: %s\n", path.Path());
if (entry.IsDirectory()) {
// subfolder
if (iterator.FollowSubdir(entry)) {
printf("start monitoring folder: %s\n", path.Path());
//printf("start monitoring folder: %s\n", path.Path());
BPrivate::BPathMonitor::StartWatching(path.Path(),
dirFlags | B_WATCH_RECURSIVELY, messenger);
}
} else {
// regular file
printf("start monitoring file: %s\n", path.Path());
//printf("start monitoring file: %s\n", path.Path());
BPrivate::BPathMonitor::StartWatching(path.Path(), fileFlags,
messenger);
}
}
if (fChangesPulse == NULL) {
BMessage message(MSG_NODE_MONITOR_PULSE);
fChangesPulse = new BMessageRunner(BMessenger(this), &message,
kChangesPulseInterval);
}
}
@ -709,6 +727,10 @@ void
GrepWindow::_StopNodeMonitoring()
{
BPrivate::BPathMonitor::StopWatching(BMessenger(this));
delete fChangesIterator;
fChangesIterator = NULL;
delete fChangesPulse;
fChangesPulse = NULL;
}
@ -721,13 +743,13 @@ GrepWindow::_OnStartCancel()
_StopNodeMonitoring();
if (fModel->fState == STATE_IDLE) {
fModel->fState = STATE_SEARCH;
fSearchResults->MakeEmpty();
if (fSearchText->TextView()->TextLength() == 0)
return;
fModel->fState = STATE_SEARCH;
fModel->AddToHistory(fSearchText->Text());
// From now on, we don't want to be notified when the
@ -756,7 +778,7 @@ GrepWindow::_OnStartCancel()
fOldPattern = fSearchText->Text();
FileIterator* iterator = new (nothrow) FolderIterator(fModel);
FileIterator* iterator = new (nothrow) InitialIterator(fModel);
fGrepper = new (nothrow) Grepper(fOldPattern.String(), fModel,
this, iterator);
if (fGrepper != NULL && fGrepper->IsValid())
@ -770,7 +792,7 @@ GrepWindow::_OnStartCancel()
delete fGrepper;
fGrepper = NULL;
}
fModel->fState = STATE_CANCEL;
fModel->fState = STATE_IDLE;
// TODO: better notification to user
fprintf(stderr, "Out of memory.\n");
}
@ -786,7 +808,7 @@ GrepWindow::_OnSearchFinished()
{
fModel->fState = STATE_IDLE;
// _StartNodeMonitoring();
_StartNodeMonitoring();
delete fGrepper;
fGrepper = NULL;
@ -816,39 +838,102 @@ GrepWindow::_OnNodeMonitorEvent(BMessage* message)
if (message->FindInt32("opcode", &opCode) != B_OK)
return;
if (fChangesIterator == NULL) {
fChangesIterator = new (nothrow) ChangesIterator(fModel);
if (fChangesIterator == NULL || !fChangesIterator->IsValid()) {
delete fChangesIterator;
fChangesIterator = NULL;
}
}
switch (opCode) {
case B_ENTRY_CREATED:
printf("B_ENTRY_CREATED\n");
break;
case B_ENTRY_REMOVED:
printf("B_ENTRY_REMOVED\n");
{
const char* name;
BString path;
if (message->FindString("path", &path) == B_OK
&& message->FindString("name", &name) == B_OK) {
path << '/' << name;
if (opCode == B_ENTRY_CREATED)
fChangesIterator->EntryAdded(path.String());
else
fChangesIterator->EntryRemoved(path.String());
}
break;
}
case B_ENTRY_MOVED:
printf("B_ENTRY_MOVED\n");
message->PrintToStream();
// TODO: If the path is now outside the folder hierarchy, it's just
// a "removed" entry. If the move happened within the hierarchy,
// it should be a combined removed/added event.
break;
case B_STAT_CHANGED:
printf("B_STAT_CHANGED\n");
break;
case B_ATTR_CHANGED:
printf("B_ATTR_CHANGED\n");
{
BString path;
if (message->FindString("path", &path) == B_OK)
fChangesIterator->EntryChanged(path.String());
break;
}
default:
printf("unkown opcode\n");
break;
}
message->PrintToStream();
fLastNodeMonitorEvent = system_time();
}
void
GrepWindow::_OnNodeMonitorPulse()
{
if (fChangesIterator == NULL)
return;
if (system_time() - fLastNodeMonitorEvent < kChangesPulseInterval) {
// wait for things to settle down before running the search for changes
return;
}
if (fModel->fState != STATE_IDLE) {
// An update or search is still in progress. New node monitor messages
// may arrive while an update is still running. They should not arrive
// during a regular search, but we want to be prepared for that anyways
// and check != STATE_IDLE.
return;
}
fOldPattern = fSearchText->Text();
fGrepper = new (nothrow) Grepper(fOldPattern.String(), fModel,
this, fChangesIterator);
if (fGrepper != NULL && fGrepper->IsValid()) {
fGrepper->Start();
fChangesIterator = NULL;
fModel->fState = STATE_UPDATE;
} else {
// roll back in case of problems
if (fGrepper == NULL)
delete fChangesIterator;
else {
// Grepper owns iterator
delete fGrepper;
fGrepper = NULL;
}
fprintf(stderr, "Out of memory.\n");
}
}
void
GrepWindow::_OnReportFileName(BMessage* message)
{
fSearchText->SetText(message->FindString("filename"));
if (fModel->fState != STATE_UPDATE)
fSearchText->SetText(message->FindString("filename"));
}
void
GrepWindow::_OnReportResult(BMessage* message)
{
@ -856,14 +941,36 @@ GrepWindow::_OnReportResult(BMessage* message)
if (message->FindRef("ref", &ref) != B_OK)
return;
BStringItem* item = new ResultItem(ref);
fSearchResults->AddItem(item);
item->SetExpanded(fModel->fShowContents);
type_code type;
int32 count;
message->GetInfo("text", &type, &count);
BStringItem* item = NULL;
if (fModel->fState == STATE_UPDATE) {
int32 index;
item = fSearchResults->FindItem(ref, &index);
if (item) {
// remove any sub items
while (true) {
BListItem* subItem = fSearchResults->FullListItemAt(index + 1);
if (subItem && subItem->OutlineLevel() > 0)
delete fSearchResults->RemoveItem(index + 1);
else
break;
}
if (count == 0) {
// remove file item itself
delete fSearchResults->RemoveItem(index);
return;
}
}
}
if (item == NULL) {
item = new ResultItem(ref);
fSearchResults->AddItem(item);
item->SetExpanded(fModel->fShowContents);
}
const char* buf;
while (message->FindString("text", --count, &buf) == B_OK) {
uchar* temp = (uchar*)strdup(buf);
@ -882,8 +989,7 @@ GrepWindow::_OnReportResult(BMessage* message)
++ptr;
}
fSearchResults->AddUnder(
new BStringItem((const char*)temp), item);
fSearchResults->AddUnder(new BStringItem((const char*)temp), item);
free(temp);
}

View File

@ -28,6 +28,8 @@
#include "Model.h"
#include "GrepListView.h"
class BMessageRunner;
class ChangesIterator;
class Grepper;
class GrepWindow : public BWindow {
@ -60,6 +62,7 @@ private:
void _OnStartCancel();
void _OnSearchFinished();
void _OnNodeMonitorEvent(BMessage* message);
void _OnNodeMonitorPulse();
void _OnReportFileName(BMessage* message);
void _OnReportResult(BMessage* message);
void _OnReportError(BMessage* message);
@ -135,6 +138,8 @@ private:
BString fOldPattern;
Model* fModel;
bigtime_t fLastNodeMonitorEvent;
ChangesIterator* fChangesIterator;
BMessageRunner* fChangesPulse;
BFilePanel* fFilePanel;
};

View File

@ -224,7 +224,7 @@ Grepper::_GrepperThread()
message.AddString("text", tempString);
}
if (message.HasString("text"))
if (message.HasString("text") || fIterator->NotifyNegatives())
fTarget.SendMessage(&message);
fclose(results);

View File

@ -21,7 +21,7 @@
* DEALINGS IN THE SOFTWARE.
*/
#include "FolderIterator.h"
#include "InitialIterator.h"
#include <new>
#include <stdio.h>
@ -37,10 +37,10 @@ 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 FolderIterator object.
// For the moment I don't know the life cycle of the InitialIterator object.
FolderIterator::FolderIterator(const Model* model)
InitialIterator::InitialIterator(const Model* model)
: FileIterator(),
fDirectories(10),
fCurrentDir(new (nothrow) BDirectory(&model->fDirectory)),
@ -61,7 +61,7 @@ FolderIterator::FolderIterator(const Model* model)
}
FolderIterator::~FolderIterator()
InitialIterator::~InitialIterator()
{
for (int32 i = fDirectories.CountItems() - 1; i >= 0; i--)
delete (BDirectory*)fDirectories.ItemAt(i);
@ -69,14 +69,14 @@ FolderIterator::~FolderIterator()
bool
FolderIterator::IsValid() const
InitialIterator::IsValid() const
{
return fCurrentDir != NULL;
}
bool
FolderIterator::GetNextName(char* buffer)
InitialIterator::GetNextName(char* buffer)
{
BEntry entry;
struct stat fileStat;
@ -110,14 +110,14 @@ FolderIterator::GetNextName(char* buffer)
bool
FolderIterator::NotifyNegatives() const
InitialIterator::NotifyNegatives() const
{
return false;
}
bool
FolderIterator::GetTopEntry(BEntry& entry)
InitialIterator::GetTopEntry(BEntry& entry)
{
// If the user selected one or more files, we must look
// at the "refs" inside the message that was passed into
@ -145,7 +145,7 @@ FolderIterator::GetTopEntry(BEntry& entry)
bool
FolderIterator::FollowSubdir(BEntry& entry) const
InitialIterator::FollowSubdir(BEntry& entry) const
{
if (!fRecurseDirs)
return false;
@ -166,7 +166,7 @@ FolderIterator::FollowSubdir(BEntry& entry) const
bool
FolderIterator::_GetNextEntry(BEntry& entry)
InitialIterator::_GetNextEntry(BEntry& entry)
{
if (fDirectories.CountItems() == 1)
return GetTopEntry(entry);
@ -176,7 +176,7 @@ FolderIterator::_GetNextEntry(BEntry& entry)
bool
FolderIterator::_GetSubEntry(BEntry& entry)
InitialIterator::_GetSubEntry(BEntry& entry)
{
if (!fCurrentDir)
return false;
@ -196,7 +196,7 @@ FolderIterator::_GetSubEntry(BEntry& entry)
void
FolderIterator::_ExamineSubdir(BEntry& entry)
InitialIterator::_ExamineSubdir(BEntry& entry)
{
if (!FollowSubdir(entry))
return;

View File

@ -20,8 +20,8 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef FOLDER_ITERATOR_H
#define FOLDER_ITERATOR_H
#ifndef INITIAL_ITERATOR_H
#define INITIAL_ITERATOR_H
#include <List.h>
#include <Message.h>
@ -41,10 +41,10 @@ class Model;
// Provides an interface to retrieve the next file that should be grepped
// for the search string.
class FolderIterator : public FileIterator {
class InitialIterator : public FileIterator {
public:
FolderIterator(const Model* model);
virtual ~FolderIterator();
InitialIterator(const Model* model);
virtual ~InitialIterator();
virtual bool IsValid() const;
virtual bool GetNextName(char* buffer);
@ -85,4 +85,4 @@ private:
bool fTextOnly : 1;
};
#endif // FOLDER_ITERATOR_H
#endif // INITIAL_ITERATOR_H

View File

@ -2,11 +2,13 @@ SubDir HAIKU_TOP src apps text_search ;
SetSubDirSupportedPlatformsBeOSCompatible ;
UsePrivateHeaders shared ;
UsePrivateHeaders storage ;
Application TextSearch :
ChangesIterator.cpp
FileIterator.cpp
FolderIterator.cpp
InitialIterator.cpp
GrepApp.cpp
GrepListView.cpp
Grepper.cpp
@ -14,6 +16,6 @@ Application TextSearch :
Model.cpp
TextSearch.cpp
: be tracker textencoding
: be tracker textencoding libshared.a
: TextSearch.rdef
;

View File

@ -48,6 +48,7 @@ enum {
MSG_SEARCH_TEXT,
MSG_INVOKE_ITEM,
MSG_SELECT_HISTORY,
MSG_NODE_MONITOR_PULSE,
MSG_REPORT_FILE_NAME,
MSG_REPORT_RESULT,
@ -70,7 +71,8 @@ enum {
enum state_t {
STATE_IDLE = 0,
STATE_SEARCH,
STATE_CANCEL
STATE_CANCEL,
STATE_UPDATE
};
class Model {