1 /////////////////////////////////////////////////////////////////////////////
2 // Name: samples/fswatcher/fswatcher.cpp
3 // Purpose: wxFileSystemWatcher sample
4 // Author: Bartosz Bekier
6 // Copyright: (c) Bartosz Bekier
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 #include "wx/wxprec.h"
19 #ifndef wxHAS_IMAGES_IN_RESOURCES
20 #include "../sample.xpm"
23 #include "wx/fswatcher.h"
24 #include "wx/listctrl.h"
25 #include "wx/cmdline.h"
27 // Define a new frame type: this is going to be our main frame
28 class MyFrame
: public wxFrame
31 MyFrame(const wxString
& title
);
34 // Add an entry of the specified type asking the user for the filename if
35 // the one passed to this function is empty.
36 void AddEntry(wxFSWPathType type
, wxString filename
= wxString());
38 bool CreateWatcherIfNecessary();
41 // file system watcher creation
45 void OnClear(wxCommandEvent
& WXUNUSED(event
)) { m_evtConsole
->Clear(); }
46 void OnQuit(wxCommandEvent
& WXUNUSED(event
)) { Close(true); }
47 void OnWatch(wxCommandEvent
& event
);
48 void OnFollowLinks(wxCommandEvent
& event
);
49 void OnAbout(wxCommandEvent
& event
);
51 void OnAdd(wxCommandEvent
& event
);
52 void OnAddTree(wxCommandEvent
& event
);
53 void OnRemove(wxCommandEvent
& event
);
54 void OnRemoveUpdateUI(wxUpdateUIEvent
& event
);
56 void OnFileSystemEvent(wxFileSystemWatcherEvent
& event
);
57 void LogEvent(const wxFileSystemWatcherEvent
& event
);
59 wxTextCtrl
*m_evtConsole
; // events console
60 wxListView
*m_filesList
; // list of watched paths
61 wxFileSystemWatcher
* m_watcher
; // file system watcher
62 bool m_followLinks
; // should symlinks be dereferenced
64 const static wxString LOG_FORMAT
; // how to format events
67 const wxString
MyFrame::LOG_FORMAT
= " %-12s %-36s %-36s";
69 // Define a new application type, each program should derive a class from wxApp
70 class MyApp
: public wxApp
73 // 'Main program' equivalent: the program execution "starts" here
76 if ( !wxApp::OnInit() )
79 wxLog::AddTraceMask("EventSource");
80 wxLog::AddTraceMask(wxTRACE_FSWATCHER
);
82 // create the main application window
83 m_frame
= new MyFrame("File System Watcher wxWidgets App");
85 // If we returned false here, the application would exit immediately.
89 // create the file system watcher here, because it needs an active loop
90 virtual void OnEventLoopEnter(wxEventLoopBase
* WXUNUSED(loop
))
92 if ( m_frame
->CreateWatcherIfNecessary() )
94 if ( !m_dirToWatch
.empty() )
95 m_frame
->AddEntry(wxFSWPath_Dir
, m_dirToWatch
);
99 virtual void OnInitCmdLine(wxCmdLineParser
& parser
)
101 wxApp::OnInitCmdLine(parser
);
102 parser
.AddParam("directory to watch",
103 wxCMD_LINE_VAL_STRING
,
104 wxCMD_LINE_PARAM_OPTIONAL
);
107 virtual bool OnCmdLineParsed(wxCmdLineParser
& parser
)
109 if ( !wxApp::OnCmdLineParsed(parser
) )
112 if ( parser
.GetParamCount() )
113 m_dirToWatch
= parser
.GetParam();
121 // The directory to watch if specified on the command line.
122 wxString m_dirToWatch
;
125 // Create a new application object: this macro will allow wxWidgets to create
126 // the application object during program execution (it's better than using a
127 // static object for many reasons) and also declares the accessor function
128 // wxGetApp() which will return the reference of the right type (i.e. MyApp and
133 // ============================================================================
135 // ============================================================================
138 MyFrame::MyFrame(const wxString
& title
)
139 : wxFrame(NULL
, wxID_ANY
, title
),
140 m_watcher(NULL
), m_followLinks(false)
142 SetIcon(wxICON(sample
));
144 // IDs for menu and buttons
147 MENU_ID_QUIT
= wxID_EXIT
,
148 MENU_ID_CLEAR
= wxID_CLEAR
,
157 // ================================================================
161 wxMenu
*menuFile
= new wxMenu
;
162 menuFile
->Append(MENU_ID_CLEAR
, "&Clear log\tCtrl-L");
163 menuFile
->AppendSeparator();
164 menuFile
->Append(MENU_ID_QUIT
, "E&xit\tAlt-X", "Quit this program");
167 wxMenu
*menuMon
= new wxMenu
;
168 wxMenuItem
* it
= menuMon
->AppendCheckItem(MENU_ID_WATCH
, "&Watch\tCtrl-W");
169 // started by default, because file system watcher is started by default
172 #if defined(__UNIX__)
173 // Let the user decide whether to dereference symlinks. If he makes the
174 // wrong choice, asserts will occur if the symlink target is also watched
175 it
= menuMon
->AppendCheckItem(MENU_ID_DEREFERENCE
,
176 "&Follow symlinks\tCtrl-F",
177 _("If checked, dereference symlinks")
180 Connect(MENU_ID_DEREFERENCE
, wxEVT_MENU
,
181 wxCommandEventHandler(MyFrame::OnFollowLinks
));
184 // the "About" item should be in the help menu
185 wxMenu
*menuHelp
= new wxMenu
;
186 menuHelp
->Append(wxID_ABOUT
, "&About\tF1", "Show about dialog");
188 // now append the freshly created menu to the menu bar...
189 wxMenuBar
*menuBar
= new wxMenuBar();
190 menuBar
->Append(menuFile
, "&File");
191 menuBar
->Append(menuMon
, "&Watch");
192 menuBar
->Append(menuHelp
, "&Help");
194 // ... and attach this menu bar to the frame
197 // ================================================================
201 wxPanel
*panel
= new wxPanel(this);
202 wxSizer
*panelSizer
= new wxGridSizer(2);
203 wxBoxSizer
*leftSizer
= new wxBoxSizer(wxVERTICAL
);
206 wxStaticText
* label
= new wxStaticText(panel
, wxID_ANY
, "Watched paths");
207 leftSizer
->Add(label
, wxSizerFlags().Center().Border(wxALL
));
210 m_filesList
= new wxListView(panel
, wxID_ANY
, wxPoint(-1,-1),
211 wxSize(300,200), wxLC_LIST
| wxLC_SINGLE_SEL
);
212 leftSizer
->Add(m_filesList
, wxSizerFlags(1).Expand());
215 wxButton
* buttonAdd
= new wxButton(panel
, BTN_ID_ADD
, "&Add");
216 wxButton
* buttonAddTree
= new wxButton(panel
, BTN_ID_ADD_TREE
, "Add &tree");
217 wxButton
* buttonRemove
= new wxButton(panel
, BTN_ID_REMOVE
, "&Remove");
218 wxSizer
*btnSizer
= new wxGridSizer(2);
219 btnSizer
->Add(buttonAdd
, wxSizerFlags().Center().Border(wxALL
));
220 btnSizer
->Add(buttonAddTree
, wxSizerFlags().Center().Border(wxALL
));
221 btnSizer
->Add(buttonRemove
, wxSizerFlags().Center().Border(wxALL
));
223 // and put it all together
224 leftSizer
->Add(btnSizer
, wxSizerFlags(0).Expand());
225 panelSizer
->Add(leftSizer
, wxSizerFlags(1).Expand());
226 panel
->SetSizerAndFit(panelSizer
);
228 // ================================================================
231 wxTextCtrl
*headerText
= new wxTextCtrl(this, wxID_ANY
, "",
232 wxDefaultPosition
, wxDefaultSize
,
234 wxString h
= wxString::Format(LOG_FORMAT
, "event", "path", "new path");
235 headerText
->SetValue(h
);
238 m_evtConsole
= new wxTextCtrl(this, wxID_ANY
, "",
239 wxDefaultPosition
, wxSize(200,200),
240 wxTE_MULTILINE
|wxTE_READONLY
|wxHSCROLL
);
242 // set monospace font to have output in nice columns
243 wxFont
font(9, wxFONTFAMILY_TELETYPE
,
244 wxFONTSTYLE_NORMAL
, wxFONTWEIGHT_NORMAL
);
245 headerText
->SetFont(font
);
246 m_evtConsole
->SetFont(font
);
248 // ================================================================
249 // laying out whole frame
251 wxBoxSizer
*sizer
= new wxBoxSizer(wxVERTICAL
);
252 sizer
->Add(panel
, wxSizerFlags(1).Expand());
253 sizer
->Add(headerText
, wxSizerFlags().Expand());
254 sizer
->Add(m_evtConsole
, wxSizerFlags(1).Expand());
255 SetSizerAndFit(sizer
);
257 // set size and position on screen
261 // ================================================================
262 // event handlers & show
265 Connect(MENU_ID_CLEAR
, wxEVT_MENU
,
266 wxCommandEventHandler(MyFrame::OnClear
));
267 Connect(MENU_ID_QUIT
, wxEVT_MENU
,
268 wxCommandEventHandler(MyFrame::OnQuit
));
269 Connect(MENU_ID_WATCH
, wxEVT_MENU
,
270 wxCommandEventHandler(MyFrame::OnWatch
));
271 Connect(wxID_ABOUT
, wxEVT_MENU
,
272 wxCommandEventHandler(MyFrame::OnAbout
));
275 Connect(BTN_ID_ADD
, wxEVT_BUTTON
,
276 wxCommandEventHandler(MyFrame::OnAdd
));
277 Connect(BTN_ID_ADD_TREE
, wxEVT_BUTTON
,
278 wxCommandEventHandler(MyFrame::OnAddTree
));
279 Connect(BTN_ID_REMOVE
, wxEVT_BUTTON
,
280 wxCommandEventHandler(MyFrame::OnRemove
));
281 Connect(BTN_ID_REMOVE
, wxEVT_UPDATE_UI
,
282 wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI
));
284 // and show itself (the frames, unlike simple controls, are not shown when
285 // created initially)
294 bool MyFrame::CreateWatcherIfNecessary()
300 Connect(wxEVT_FSWATCHER
,
301 wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent
));
306 void MyFrame::CreateWatcher()
308 wxCHECK_RET(!m_watcher
, "Watcher already initialized");
309 m_watcher
= new wxFileSystemWatcher();
310 m_watcher
->SetOwner(this);
313 // ============================================================================
315 // ============================================================================
317 void MyFrame::OnAbout(wxCommandEvent
& WXUNUSED(event
))
319 wxMessageBox("Demonstrates the usage of file system watcher, "
320 "the wxWidgets monitoring system notifying you of "
321 "changes done to your files.\n"
322 "(c) 2009 Bartosz Bekier\n",
323 "About wxWidgets File System Watcher Sample",
324 wxOK
| wxICON_INFORMATION
, this);
327 void MyFrame::OnWatch(wxCommandEvent
& event
)
329 wxLogDebug("%s start=%d", __WXFUNCTION__
, event
.IsChecked());
331 if (event
.IsChecked())
333 wxCHECK_RET(!m_watcher
, "Watcher already initialized");
338 wxCHECK_RET(m_watcher
, "Watcher not initialized");
339 m_filesList
->DeleteAllItems();
344 void MyFrame::OnFollowLinks(wxCommandEvent
& event
)
346 m_followLinks
= event
.IsChecked();
349 void MyFrame::OnAdd(wxCommandEvent
& WXUNUSED(event
))
351 AddEntry(wxFSWPath_Dir
);
354 void MyFrame::OnAddTree(wxCommandEvent
& WXUNUSED(event
))
356 AddEntry(wxFSWPath_Tree
);
359 void MyFrame::AddEntry(wxFSWPathType type
, wxString filename
)
361 if ( filename
.empty() )
363 // TODO account for adding the files as well
364 filename
= wxDirSelector("Choose a folder to watch", "",
365 wxDD_DEFAULT_STYLE
| wxDD_DIR_MUST_EXIST
);
366 if ( filename
.empty() )
370 wxCHECK_RET(m_watcher
, "Watcher not initialized");
372 wxLogDebug("Adding %s: '%s'",
374 type
== wxFSWPath_Dir
? "directory" : "directory tree");
379 // This will tell wxFileSystemWatcher whether to dereference symlinks
380 wxFileName fn
= wxFileName::DirName(filename
);
389 ok
= m_watcher
->Add(fn
);
394 ok
= m_watcher
->AddTree(fn
);
400 wxFAIL_MSG( "Unexpected path type." );
405 wxLogError("Error adding '%s' to watched paths", filename
);
409 // Prepend 'prefix' to the filepath, partly for display
410 // but mostly so that OnRemove() can work out the correct way to remove it
411 m_filesList
->InsertItem(m_filesList
->GetItemCount(),
412 prefix
+ wxFileName::DirName(filename
).GetFullPath());
415 void MyFrame::OnRemove(wxCommandEvent
& WXUNUSED(event
))
417 wxCHECK_RET(m_watcher
, "Watcher not initialized");
418 long idx
= m_filesList
->GetFirstSelected();
423 wxString path
= m_filesList
->GetItemText(idx
).Mid(6);
425 // This will tell wxFileSystemWatcher whether to dereference symlinks
426 wxFileName fn
= wxFileName::DirName(path
);
432 // TODO we know it is a dir, but it doesn't have to be
433 if (m_filesList
->GetItemText(idx
).StartsWith("Dir: "))
435 ret
= m_watcher
->Remove(fn
);
437 else if (m_filesList
->GetItemText(idx
).StartsWith("Tree: "))
439 ret
= m_watcher
->RemoveTree(fn
);
443 wxFAIL_MSG("Unexpected item in wxListView.");
448 wxLogError("Error removing '%s' from watched paths", path
);
452 m_filesList
->DeleteItem(idx
);
456 void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent
& event
)
458 event
.Enable(m_filesList
->GetFirstSelected() != wxNOT_FOUND
);
461 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent
& event
)
463 // TODO remove when code is rock-solid
464 wxLogTrace(wxTRACE_FSWATCHER
, "*** %s ***", event
.ToString());
467 int type
= event
.GetChangeType();
468 if ((type
== wxFSW_EVENT_DELETE
) || (type
== wxFSW_EVENT_RENAME
))
470 // If path is one of our watched dirs, we need to react to this
471 // otherwise there'll be asserts if later we try to remove it
472 wxString eventpath
= event
.GetPath().GetFullPath();
474 for (size_t n
= m_filesList
->GetItemCount(); n
> 0; --n
)
476 wxString path
, foo
= m_filesList
->GetItemText(n
-1);
477 if ((!m_filesList
->GetItemText(n
-1).StartsWith("Dir: ", &path
)) &&
478 (!m_filesList
->GetItemText(n
-1).StartsWith("Tree: ", &path
)))
480 wxFAIL_MSG("Unexpected item in wxListView.");
482 if (path
== eventpath
)
484 if (type
== wxFSW_EVENT_DELETE
)
486 m_filesList
->DeleteItem(n
-1);
490 // At least in wxGTK, we'll never get here: renaming the top
491 // watched dir gives IN_MOVE_SELF and no new-name info.
492 // However I'll leave the code in case other platforms do
493 wxString newname
= event
.GetNewPath().GetFullPath();
494 if (newname
.empty() ||
495 newname
== event
.GetPath().GetFullPath())
497 // Just in case either of these are possible...
498 wxLogTrace(wxTRACE_FSWATCHER
,
499 "Invalid attempt to rename to %s", newname
);
503 m_filesList
->GetItemText(n
-1).StartsWith("Dir: ") ?
505 m_filesList
->SetItemText(n
-1, prefix
+ newname
);
508 // Don't break: a filepath may have been added more than once
514 wxString msg
= wxString::Format(
515 "Your watched path %s has been deleted or renamed\n",
517 m_evtConsole
->AppendText(msg
);
523 static wxString
GetFSWEventChangeTypeName(int changeType
)
527 case wxFSW_EVENT_CREATE
:
529 case wxFSW_EVENT_DELETE
:
531 case wxFSW_EVENT_RENAME
:
533 case wxFSW_EVENT_MODIFY
:
535 case wxFSW_EVENT_ACCESS
:
537 case wxFSW_EVENT_ATTRIB
: // Currently this is wxGTK-only
540 case wxFSW_EVENT_UNMOUNT
: // Currently this is wxGTK-only
543 case wxFSW_EVENT_WARNING
:
545 case wxFSW_EVENT_ERROR
:
549 return "INVALID_TYPE";
552 void MyFrame::LogEvent(const wxFileSystemWatcherEvent
& event
)
554 wxString entry
= wxString::Format(LOG_FORMAT
+ "\n",
555 GetFSWEventChangeTypeName(event
.GetChangeType()),
556 event
.GetPath().GetFullPath(),
557 event
.GetNewPath().GetFullPath());
558 m_evtConsole
->AppendText(entry
);