X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/5276b0a53cef4815230e39b54d2ecda14f72cbd1..2a45803fc3877afd0ae3ce356dfe216505165882:/samples/fswatcher/fswatcher.cpp?ds=sidebyside diff --git a/samples/fswatcher/fswatcher.cpp b/samples/fswatcher/fswatcher.cpp index 907f46ceaf..890fd45ff5 100644 --- a/samples/fswatcher/fswatcher.cpp +++ b/samples/fswatcher/fswatcher.cpp @@ -17,12 +17,13 @@ #include "wx/wx.h" #endif -#ifndef __WXMSW__ +#ifndef wxHAS_IMAGES_IN_RESOURCES #include "../sample.xpm" #endif #include "wx/fswatcher.h" #include "wx/listctrl.h" +#include "wx/cmdline.h" // Define a new frame type: this is going to be our main frame class MyFrame : public wxFrame @@ -31,19 +32,27 @@ public: MyFrame(const wxString& title); virtual ~MyFrame(); + // Add an entry of the specified type asking the user for the filename if + // the one passed to this function is empty. + void AddEntry(wxFSWPathType type, wxString filename = wxString()); + + bool CreateWatcherIfNecessary(); + private: // file system watcher creation - void OnEventLoopEnter(); void CreateWatcher(); // event handlers void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); } void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); } void OnWatch(wxCommandEvent& event); + void OnFollowLinks(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnAdd(wxCommandEvent& event); + void OnAddTree(wxCommandEvent& event); void OnRemove(wxCommandEvent& event); + void OnRemoveUpdateUI(wxUpdateUIEvent& event); void OnFileSystemEvent(wxFileSystemWatcherEvent& event); void LogEvent(const wxFileSystemWatcherEvent& event); @@ -51,8 +60,7 @@ private: wxTextCtrl *m_evtConsole; // events console wxListView *m_filesList; // list of watched paths wxFileSystemWatcher* m_watcher; // file system watcher - - friend class MyApp; + bool m_followLinks; // should symlinks be dereferenced const static wxString LOG_FORMAT; // how to format events }; @@ -66,6 +74,9 @@ public: // 'Main program' equivalent: the program execution "starts" here virtual bool OnInit() { + if ( !wxApp::OnInit() ) + return false; + wxLog::AddTraceMask("EventSource"); wxLog::AddTraceMask(wxTRACE_FSWATCHER); @@ -79,11 +90,37 @@ public: // create the file system watcher here, because it needs an active loop virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop)) { - m_frame->OnEventLoopEnter(); + if ( m_frame->CreateWatcherIfNecessary() ) + { + if ( !m_dirToWatch.empty() ) + m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch); + } + } + + virtual void OnInitCmdLine(wxCmdLineParser& parser) + { + wxApp::OnInitCmdLine(parser); + parser.AddParam("directory to watch", + wxCMD_LINE_VAL_STRING, + wxCMD_LINE_PARAM_OPTIONAL); + } + + virtual bool OnCmdLineParsed(wxCmdLineParser& parser) + { + if ( !wxApp::OnCmdLineParsed(parser) ) + return false; + + if ( parser.GetParamCount() ) + m_dirToWatch = parser.GetParam(); + + return true; } private: MyFrame *m_frame; + + // The directory to watch if specified on the command line. + wxString m_dirToWatch; }; // Create a new application object: this macro will allow wxWidgets to create @@ -101,7 +138,7 @@ IMPLEMENT_APP(MyApp) // frame constructor MyFrame::MyFrame(const wxString& title) : wxFrame(NULL, wxID_ANY, title), - m_watcher(NULL) + m_watcher(NULL), m_followLinks(false) { SetIcon(wxICON(sample)); @@ -111,9 +148,11 @@ MyFrame::MyFrame(const wxString& title) MENU_ID_QUIT = wxID_EXIT, MENU_ID_CLEAR = wxID_CLEAR, MENU_ID_WATCH = 101, + MENU_ID_DEREFERENCE, BTN_ID_ADD = 200, - BTN_ID_REMOVE = 201, + BTN_ID_ADD_TREE, + BTN_ID_REMOVE }; // ================================================================ @@ -131,9 +170,21 @@ MyFrame::MyFrame(const wxString& title) // started by default, because file system watcher is started by default it->Check(true); +#if defined(__UNIX__) + // Let the user decide whether to dereference symlinks. If he makes the + // wrong choice, asserts will occur if the symlink target is also watched + it = menuMon->AppendCheckItem(MENU_ID_DEREFERENCE, + "&Follow symlinks\tCtrl-F", + _("If checked, dereference symlinks") + ); + it->Check(false); + Connect(MENU_ID_DEREFERENCE, wxEVT_COMMAND_MENU_SELECTED, + wxCommandEventHandler(MyFrame::OnFollowLinks)); +#endif // __UNIX__ + // the "About" item should be in the help menu wxMenu *menuHelp = new wxMenu; - menuHelp->Append(wxID_ABOUT, "&About...\tF1", "Show about dialog"); + menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog"); // now append the freshly created menu to the menu bar... wxMenuBar *menuBar = new wxMenuBar(); @@ -163,9 +214,11 @@ MyFrame::MyFrame(const wxString& title) // buttons wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add"); + wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree"); wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove"); wxSizer *btnSizer = new wxGridSizer(2); btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL)); + btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL)); btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL)); // and put it all together @@ -222,8 +275,12 @@ MyFrame::MyFrame(const wxString& title) // buttons Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MyFrame::OnAdd)); + Connect(BTN_ID_ADD_TREE, wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(MyFrame::OnAddTree)); Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MyFrame::OnRemove)); + Connect(BTN_ID_REMOVE, wxEVT_UPDATE_UI, + wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI)); // and show itself (the frames, unlike simple controls, are not shown when // created initially) @@ -235,14 +292,16 @@ MyFrame::~MyFrame() delete m_watcher; } -void MyFrame::OnEventLoopEnter() +bool MyFrame::CreateWatcherIfNecessary() { if (m_watcher) - return; + return false; CreateWatcher(); Connect(wxEVT_FSWATCHER, wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent)); + + return true; } void MyFrame::CreateWatcher() @@ -283,26 +342,75 @@ void MyFrame::OnWatch(wxCommandEvent& event) } } +void MyFrame::OnFollowLinks(wxCommandEvent& event) +{ + m_followLinks = event.IsChecked(); +} + void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event)) { + AddEntry(wxFSWPath_Dir); +} + +void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event)) +{ + AddEntry(wxFSWPath_Tree); +} + +void MyFrame::AddEntry(wxFSWPathType type, wxString filename) +{ + if ( filename.empty() ) + { + // TODO account for adding the files as well + filename = wxDirSelector("Choose a folder to watch", "", + wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if ( filename.empty() ) + return; + } + wxCHECK_RET(m_watcher, "Watcher not initialized"); - // TODO account for adding the files as well - const wxString& dir = wxDirSelector("Choose a folder to watch", "", - wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); - if ( dir.empty() ) - return; + wxLogDebug("Adding %s: '%s'", + filename, + type == wxFSWPath_Dir ? "directory" : "directory tree"); - wxLogDebug("Adding directory: '%s'", dir); + wxString prefix; + bool ok = false; - if (!m_watcher->Add(wxFileName::DirName(dir), wxFSW_EVENT_ALL)) + // This will tell wxFileSystemWatcher whether to dereference symlinks + wxFileName fn = wxFileName::DirName(filename); + if (!m_followLinks) { - wxLogError("Error adding '%s' to watched paths", dir); + fn.DontFollowLink(); } - else + + switch ( type ) + { + case wxFSWPath_Dir: + ok = m_watcher->Add(fn); + prefix = "Dir: "; + break; + + case wxFSWPath_Tree: + ok = m_watcher->AddTree(fn); + prefix = "Tree: "; + break; + + case wxFSWPath_File: + case wxFSWPath_None: + wxFAIL_MSG( "Unexpected path type." ); + } + + if (!ok) { - m_filesList->InsertItem(m_filesList->GetItemCount(), dir); + wxLogError("Error adding '%s' to watched paths", filename); + return; } + + // Prepend 'prefix' to the filepath, partly for display + // but mostly so that OnRemove() can work out the correct way to remove it + m_filesList->InsertItem(m_filesList->GetItemCount(), + prefix + wxFileName::DirName(filename).GetFullPath()); } void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event)) @@ -312,10 +420,31 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event)) if (idx == -1) return; - wxString path = m_filesList->GetItemText(idx); + bool ret = false; + wxString path = m_filesList->GetItemText(idx).Mid(6); + + // This will tell wxFileSystemWatcher whether to dereference symlinks + wxFileName fn = wxFileName::DirName(path); + if (!m_followLinks) + { + fn.DontFollowLink(); + } // TODO we know it is a dir, but it doesn't have to be - if (!m_watcher->Remove(wxFileName::DirName(path))) + if (m_filesList->GetItemText(idx).StartsWith("Dir: ")) + { + ret = m_watcher->Remove(fn); + } + else if (m_filesList->GetItemText(idx).StartsWith("Tree: ")) + { + ret = m_watcher->RemoveTree(fn); + } + else + { + wxFAIL_MSG("Unexpected item in wxListView."); + } + + if (!ret) { wxLogError("Error removing '%s' from watched paths", path); } @@ -325,11 +454,70 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event)) } } +void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event) +{ + event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND); +} + void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event) { // TODO remove when code is rock-solid - wxLogDebug(wxTRACE_FSWATCHER, "*** %s ***", event.ToString()); + wxLogTrace(wxTRACE_FSWATCHER, "*** %s ***", event.ToString()); LogEvent(event); + + int type = event.GetChangeType(); + if ((type == wxFSW_EVENT_DELETE) || (type == wxFSW_EVENT_RENAME)) + { + // If path is one of our watched dirs, we need to react to this + // otherwise there'll be asserts if later we try to remove it + wxString eventpath = event.GetPath().GetFullPath(); + bool found(false); + for (size_t n = m_filesList->GetItemCount(); n > 0; --n) + { + wxString path, foo = m_filesList->GetItemText(n-1); + if ((!m_filesList->GetItemText(n-1).StartsWith("Dir: ", &path)) && + (!m_filesList->GetItemText(n-1).StartsWith("Tree: ", &path))) + { + wxFAIL_MSG("Unexpected item in wxListView."); + } + if (path == eventpath) + { + if (type == wxFSW_EVENT_DELETE) + { + m_filesList->DeleteItem(n-1); + } + else + { + // At least in wxGTK, we'll never get here: renaming the top + // watched dir gives IN_MOVE_SELF and no new-name info. + // However I'll leave the code in case other platforms do + wxString newname = event.GetNewPath().GetFullPath(); + if (newname.empty() || + newname == event.GetPath().GetFullPath()) + { + // Just in case either of these are possible... + wxLogTrace(wxTRACE_FSWATCHER, + "Invalid attempt to rename to %s", newname); + return; + } + wxString prefix = + m_filesList->GetItemText(n-1).StartsWith("Dir: ") ? + "Dir: " : "Tree: "; + m_filesList->SetItemText(n-1, prefix + newname); + } + found = true; + // Don't break: a filepath may have been added more than once + } + } + + if (found) + { + wxString msg = wxString::Format( + "Your watched path %s has been deleted or renamed\n", + eventpath); + m_evtConsole->AppendText(msg); + } + } } @@ -347,6 +535,16 @@ static wxString GetFSWEventChangeTypeName(int changeType) return "MODIFY"; case wxFSW_EVENT_ACCESS: return "ACCESS"; + case wxFSW_EVENT_ATTRIB: // Currently this is wxGTK-only + return "ATTRIBUTE"; +#ifdef wxHAS_INOTIFY + case wxFSW_EVENT_UNMOUNT: // Currently this is wxGTK-only + return "UNMOUNT"; +#endif + case wxFSW_EVENT_WARNING: + return "WARNING"; + case wxFSW_EVENT_ERROR: + return "ERROR"; } return "INVALID_TYPE";