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 {