From 962a6c67e9f80f14d48158c5666897f3ca2847e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Mon, 4 Aug 2008 21:51:33 +0000 Subject: [PATCH] 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 --- src/apps/text_search/ChangesIterator.cpp | 108 ++++++++++++ src/apps/text_search/ChangesIterator.h | 48 +++++ src/apps/text_search/GrepListView.cpp | 18 ++ src/apps/text_search/GrepListView.h | 3 + src/apps/text_search/GrepWindow.cpp | 164 ++++++++++++++---- src/apps/text_search/GrepWindow.h | 5 + src/apps/text_search/Grepper.cpp | 2 +- ...FolderIterator.cpp => InitialIterator.cpp} | 24 +-- .../{FolderIterator.h => InitialIterator.h} | 12 +- src/apps/text_search/Jamfile | 6 +- src/apps/text_search/Model.h | 4 +- 11 files changed, 343 insertions(+), 51 deletions(-) create mode 100644 src/apps/text_search/ChangesIterator.cpp create mode 100644 src/apps/text_search/ChangesIterator.h rename src/apps/text_search/{FolderIterator.cpp => InitialIterator.cpp} (89%) rename src/apps/text_search/{FolderIterator.h => InitialIterator.h} (92%) diff --git a/src/apps/text_search/ChangesIterator.cpp b/src/apps/text_search/ChangesIterator.cpp new file mode 100644 index 0000000000..13114fdbcb --- /dev/null +++ b/src/apps/text_search/ChangesIterator.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT license. + */ + +#include "ChangesIterator.h" + +#include +#include +#include + +#include + +#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); +} diff --git a/src/apps/text_search/ChangesIterator.h b/src/apps/text_search/ChangesIterator.h new file mode 100644 index 0000000000..52611f3171 --- /dev/null +++ b/src/apps/text_search/ChangesIterator.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT license. + */ +#ifndef CHANGES_ITERATOR_H +#define CHANGES_ITERATOR_H + +#include +#include + +#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 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 diff --git a/src/apps/text_search/GrepListView.cpp b/src/apps/text_search/GrepListView.cpp index 4e855964d3..6e139bdb89 100644 --- a/src/apps/text_search/GrepListView.cpp +++ b/src/apps/text_search/GrepListView.cpp @@ -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(FullListItemAt(i)); + if (item == NULL) + continue; + if (item->ref == ref) { + *_index = i; + return item; + } + } + *_index = -1; + return NULL; +} diff --git a/src/apps/text_search/GrepListView.h b/src/apps/text_search/GrepListView.h index 61e0537303..a300b84d25 100644 --- a/src/apps/text_search/GrepListView.h +++ b/src/apps/text_search/GrepListView.h @@ -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 diff --git a/src/apps/text_search/GrepWindow.cpp b/src/apps/text_search/GrepWindow.cpp index 156d51cc70..5073b65ee7 100644 --- a/src/apps/text_search/GrepWindow.cpp +++ b/src/apps/text_search/GrepWindow.cpp @@ -33,19 +33,23 @@ #include #include #include +#include #include #include #include #include #include -#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); } diff --git a/src/apps/text_search/GrepWindow.h b/src/apps/text_search/GrepWindow.h index ffebaf85f0..39f7bba497 100644 --- a/src/apps/text_search/GrepWindow.h +++ b/src/apps/text_search/GrepWindow.h @@ -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; }; diff --git a/src/apps/text_search/Grepper.cpp b/src/apps/text_search/Grepper.cpp index ba730d6ff5..d65019d7d9 100644 --- a/src/apps/text_search/Grepper.cpp +++ b/src/apps/text_search/Grepper.cpp @@ -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); diff --git a/src/apps/text_search/FolderIterator.cpp b/src/apps/text_search/InitialIterator.cpp similarity index 89% rename from src/apps/text_search/FolderIterator.cpp rename to src/apps/text_search/InitialIterator.cpp index 145fc7f366..68db11b367 100644 --- a/src/apps/text_search/FolderIterator.cpp +++ b/src/apps/text_search/InitialIterator.cpp @@ -21,7 +21,7 @@ * DEALINGS IN THE SOFTWARE. */ -#include "FolderIterator.h" +#include "InitialIterator.h" #include #include @@ -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; diff --git a/src/apps/text_search/FolderIterator.h b/src/apps/text_search/InitialIterator.h similarity index 92% rename from src/apps/text_search/FolderIterator.h rename to src/apps/text_search/InitialIterator.h index d6fba5c88d..e43cdb2b2a 100644 --- a/src/apps/text_search/FolderIterator.h +++ b/src/apps/text_search/InitialIterator.h @@ -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 #include @@ -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 diff --git a/src/apps/text_search/Jamfile b/src/apps/text_search/Jamfile index 865ef1db7a..e3a2cdb691 100644 --- a/src/apps/text_search/Jamfile +++ b/src/apps/text_search/Jamfile @@ -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 ; diff --git a/src/apps/text_search/Model.h b/src/apps/text_search/Model.h index 5452375d32..43509ded9c 100644 --- a/src/apps/text_search/Model.h +++ b/src/apps/text_search/Model.h @@ -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 {