Handle deletion of watched directories in wxFileSystemWatcher sample.
[wxWidgets.git] / samples / fswatcher / fswatcher.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: samples/fswatcher/fswatcher.cpp
3 // Purpose: wxFileSystemWatcher sample
4 // Author: Bartosz Bekier
5 // Created: 2009-06-27
6 // RCS-ID: $Id$
7 // Copyright: (c) Bartosz Bekier
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #include "wx/wxprec.h"
12
13 #ifdef __BORLANDC__
14 #pragma hdrstop
15 #endif
16 #ifndef WX_PRECOMP
17 #include "wx/wx.h"
18 #endif
19
20 #ifndef wxHAS_IMAGES_IN_RESOURCES
21 #include "../sample.xpm"
22 #endif
23
24 #include "wx/fswatcher.h"
25 #include "wx/listctrl.h"
26 #include "wx/cmdline.h"
27
28 // Define a new frame type: this is going to be our main frame
29 class MyFrame : public wxFrame
30 {
31 public:
32 MyFrame(const wxString& title);
33 virtual ~MyFrame();
34
35 // Add an entry of the specified type asking the user for the filename if
36 // the one passed to this function is empty.
37 void AddEntry(wxFSWPathType type, wxString filename = wxString());
38
39 bool CreateWatcherIfNecessary();
40
41 private:
42 // file system watcher creation
43 void CreateWatcher();
44
45 // event handlers
46 void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
47 void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
48 void OnWatch(wxCommandEvent& event);
49 void OnAbout(wxCommandEvent& event);
50
51 void OnAdd(wxCommandEvent& event);
52 void OnAddTree(wxCommandEvent& event);
53 void OnRemove(wxCommandEvent& event);
54
55 void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
56 void LogEvent(const wxFileSystemWatcherEvent& event);
57
58 wxTextCtrl *m_evtConsole; // events console
59 wxListView *m_filesList; // list of watched paths
60 wxFileSystemWatcher* m_watcher; // file system watcher
61
62 const static wxString LOG_FORMAT; // how to format events
63 };
64
65 const wxString MyFrame::LOG_FORMAT = " %-12s %-36s %-36s";
66
67 // Define a new application type, each program should derive a class from wxApp
68 class MyApp : public wxApp
69 {
70 public:
71 // 'Main program' equivalent: the program execution "starts" here
72 virtual bool OnInit()
73 {
74 if ( !wxApp::OnInit() )
75 return false;
76
77 wxLog::AddTraceMask("EventSource");
78 wxLog::AddTraceMask(wxTRACE_FSWATCHER);
79
80 // create the main application window
81 m_frame = new MyFrame("File System Watcher wxWidgets App");
82
83 // If we returned false here, the application would exit immediately.
84 return true;
85 }
86
87 // create the file system watcher here, because it needs an active loop
88 virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop))
89 {
90 if ( m_frame->CreateWatcherIfNecessary() )
91 {
92 if ( !m_dirToWatch.empty() )
93 m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch);
94 }
95 }
96
97 virtual void OnInitCmdLine(wxCmdLineParser& parser)
98 {
99 wxApp::OnInitCmdLine(parser);
100 parser.AddParam("directory to watch",
101 wxCMD_LINE_VAL_STRING,
102 wxCMD_LINE_PARAM_OPTIONAL);
103 }
104
105 virtual bool OnCmdLineParsed(wxCmdLineParser& parser)
106 {
107 if ( !wxApp::OnCmdLineParsed(parser) )
108 return false;
109
110 if ( parser.GetParamCount() )
111 m_dirToWatch = parser.GetParam();
112
113 return true;
114 }
115
116 private:
117 MyFrame *m_frame;
118
119 // The directory to watch if specified on the command line.
120 wxString m_dirToWatch;
121 };
122
123 // Create a new application object: this macro will allow wxWidgets to create
124 // the application object during program execution (it's better than using a
125 // static object for many reasons) and also declares the accessor function
126 // wxGetApp() which will return the reference of the right type (i.e. MyApp and
127 // not wxApp)
128 IMPLEMENT_APP(MyApp)
129
130
131 // ============================================================================
132 // implementation
133 // ============================================================================
134
135 // frame constructor
136 MyFrame::MyFrame(const wxString& title)
137 : wxFrame(NULL, wxID_ANY, title),
138 m_watcher(NULL)
139 {
140 SetIcon(wxICON(sample));
141
142 // IDs for menu and buttons
143 enum
144 {
145 MENU_ID_QUIT = wxID_EXIT,
146 MENU_ID_CLEAR = wxID_CLEAR,
147 MENU_ID_WATCH = 101,
148
149 BTN_ID_ADD = 200,
150 BTN_ID_ADD_TREE,
151 BTN_ID_REMOVE
152 };
153
154 // ================================================================
155 // menu
156
157 // create a menu bar
158 wxMenu *menuFile = new wxMenu;
159 menuFile->Append(MENU_ID_CLEAR, "&Clear log\tCtrl-L");
160 menuFile->AppendSeparator();
161 menuFile->Append(MENU_ID_QUIT, "E&xit\tAlt-X", "Quit this program");
162
163 // "Watch" menu
164 wxMenu *menuMon = new wxMenu;
165 wxMenuItem* it = menuMon->AppendCheckItem(MENU_ID_WATCH, "&Watch\tCtrl-W");
166 // started by default, because file system watcher is started by default
167 it->Check(true);
168
169 // the "About" item should be in the help menu
170 wxMenu *menuHelp = new wxMenu;
171 menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
172
173 // now append the freshly created menu to the menu bar...
174 wxMenuBar *menuBar = new wxMenuBar();
175 menuBar->Append(menuFile, "&File");
176 menuBar->Append(menuMon, "&Watch");
177 menuBar->Append(menuHelp, "&Help");
178
179 // ... and attach this menu bar to the frame
180 SetMenuBar(menuBar);
181
182 // ================================================================
183 // upper panel
184
185 // panel
186 wxPanel *panel = new wxPanel(this);
187 wxSizer *panelSizer = new wxGridSizer(2);
188 wxBoxSizer *leftSizer = new wxBoxSizer(wxVERTICAL);
189
190 // label
191 wxStaticText* label = new wxStaticText(panel, wxID_ANY, "Watched paths");
192 leftSizer->Add(label, wxSizerFlags().Center().Border(wxALL));
193
194 // list of files
195 m_filesList = new wxListView(panel, wxID_ANY, wxPoint(-1,-1),
196 wxSize(300,200), wxLC_LIST | wxLC_SINGLE_SEL);
197 leftSizer->Add(m_filesList, wxSizerFlags(1).Expand());
198
199 // buttons
200 wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add");
201 wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree");
202 wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove");
203 wxSizer *btnSizer = new wxGridSizer(2);
204 btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL));
205 btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL));
206 btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL));
207
208 // and put it all together
209 leftSizer->Add(btnSizer, wxSizerFlags(0).Expand());
210 panelSizer->Add(leftSizer, wxSizerFlags(1).Expand());
211 panel->SetSizerAndFit(panelSizer);
212
213 // ================================================================
214 // lower panel
215
216 wxTextCtrl *headerText = new wxTextCtrl(this, wxID_ANY, "",
217 wxDefaultPosition, wxDefaultSize,
218 wxTE_READONLY);
219 wxString h = wxString::Format(LOG_FORMAT, "event", "path", "new path");
220 headerText->SetValue(h);
221
222 // event console
223 m_evtConsole = new wxTextCtrl(this, wxID_ANY, "",
224 wxDefaultPosition, wxSize(200,200),
225 wxTE_MULTILINE|wxTE_READONLY|wxHSCROLL);
226
227 // set monospace font to have output in nice columns
228 wxFont font(9, wxFONTFAMILY_TELETYPE,
229 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
230 headerText->SetFont(font);
231 m_evtConsole->SetFont(font);
232
233 // ================================================================
234 // laying out whole frame
235
236 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
237 sizer->Add(panel, wxSizerFlags(1).Expand());
238 sizer->Add(headerText, wxSizerFlags().Expand());
239 sizer->Add(m_evtConsole, wxSizerFlags(1).Expand());
240 SetSizerAndFit(sizer);
241
242 // set size and position on screen
243 SetSize(800, 600);
244 CentreOnScreen();
245
246 // ================================================================
247 // event handlers & show
248
249 // menu
250 Connect(MENU_ID_CLEAR, wxEVT_COMMAND_MENU_SELECTED,
251 wxCommandEventHandler(MyFrame::OnClear));
252 Connect(MENU_ID_QUIT, wxEVT_COMMAND_MENU_SELECTED,
253 wxCommandEventHandler(MyFrame::OnQuit));
254 Connect(MENU_ID_WATCH, wxEVT_COMMAND_MENU_SELECTED,
255 wxCommandEventHandler(MyFrame::OnWatch));
256 Connect(wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED,
257 wxCommandEventHandler(MyFrame::OnAbout));
258
259 // buttons
260 Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED,
261 wxCommandEventHandler(MyFrame::OnAdd));
262 Connect(BTN_ID_ADD_TREE, wxEVT_COMMAND_BUTTON_CLICKED,
263 wxCommandEventHandler(MyFrame::OnAddTree));
264 Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
265 wxCommandEventHandler(MyFrame::OnRemove));
266
267 // and show itself (the frames, unlike simple controls, are not shown when
268 // created initially)
269 Show(true);
270 }
271
272 MyFrame::~MyFrame()
273 {
274 delete m_watcher;
275 }
276
277 bool MyFrame::CreateWatcherIfNecessary()
278 {
279 if (m_watcher)
280 return false;
281
282 CreateWatcher();
283 Connect(wxEVT_FSWATCHER,
284 wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
285
286 return true;
287 }
288
289 void MyFrame::CreateWatcher()
290 {
291 wxCHECK_RET(!m_watcher, "Watcher already initialized");
292 m_watcher = new wxFileSystemWatcher();
293 m_watcher->SetOwner(this);
294 }
295
296 // ============================================================================
297 // event handlers
298 // ============================================================================
299
300 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
301 {
302 wxMessageBox("Demonstrates the usage of file system watcher, "
303 "the wxWidgets monitoring system notifying you of "
304 "changes done to your files.\n"
305 "(c) 2009 Bartosz Bekier\n",
306 "About wxWidgets File System Watcher Sample",
307 wxOK | wxICON_INFORMATION, this);
308 }
309
310 void MyFrame::OnWatch(wxCommandEvent& event)
311 {
312 wxLogDebug("%s start=%d", __WXFUNCTION__, event.IsChecked());
313
314 if (event.IsChecked())
315 {
316 wxCHECK_RET(!m_watcher, "Watcher already initialized");
317 CreateWatcher();
318 }
319 else
320 {
321 wxCHECK_RET(m_watcher, "Watcher not initialized");
322 m_filesList->DeleteAllItems();
323 wxDELETE(m_watcher);
324 }
325 }
326
327 void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
328 {
329 AddEntry(wxFSWPath_Dir);
330 }
331
332 void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event))
333 {
334 AddEntry(wxFSWPath_Tree);
335 }
336
337 void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
338 {
339 if ( filename.empty() )
340 {
341 // TODO account for adding the files as well
342 filename = wxDirSelector("Choose a folder to watch", "",
343 wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
344 if ( filename.empty() )
345 return;
346 }
347
348 wxCHECK_RET(m_watcher, "Watcher not initialized");
349
350 wxLogDebug("Adding %s: '%s'",
351 filename,
352 type == wxFSWPath_Dir ? "directory" : "directory tree");
353
354 wxString prefix;
355 bool ok = false;
356 switch ( type )
357 {
358 case wxFSWPath_Dir:
359 ok = m_watcher->Add(wxFileName::DirName(filename));
360 prefix = "Dir: ";
361 break;
362
363 case wxFSWPath_Tree:
364 ok = m_watcher->AddTree(wxFileName::DirName(filename));
365 prefix = "Tree: ";
366 break;
367
368 case wxFSWPath_File:
369 case wxFSWPath_None:
370 wxFAIL_MSG( "Unexpected path type." );
371 }
372
373 if (!ok)
374 {
375 wxLogError("Error adding '%s' to watched paths", filename);
376 return;
377 }
378
379 // Prepend 'prefix' to the filepath, partly for display
380 // but mostly so that OnRemove() can work out the correct way to remove it
381 m_filesList->InsertItem(m_filesList->GetItemCount(),
382 prefix + wxFileName::DirName(filename).GetFullPath());
383 }
384
385 void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
386 {
387 wxCHECK_RET(m_watcher, "Watcher not initialized");
388 long idx = m_filesList->GetFirstSelected();
389 if (idx == -1)
390 return;
391
392 bool ret;
393 wxString path;
394 // TODO we know it is a dir, but it doesn't have to be
395 if (m_filesList->GetItemText(idx).StartsWith("Dir: ", &path))
396 {
397 ret = m_watcher->Remove(wxFileName::DirName(path));
398 }
399 else if (m_filesList->GetItemText(idx).StartsWith("Tree: ", &path))
400 {
401 ret = m_watcher->RemoveTree(wxFileName::DirName(path));
402 }
403 else
404 {
405 wxFAIL_MSG("Unexpected item in wxListView.");
406 }
407
408 if (!ret)
409 {
410 wxLogError("Error removing '%s' from watched paths", path);
411 }
412 else
413 {
414 m_filesList->DeleteItem(idx);
415 }
416 }
417
418 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
419 {
420 // TODO remove when code is rock-solid
421 wxLogTrace(wxTRACE_FSWATCHER, "*** %s ***", event.ToString());
422 LogEvent(event);
423
424 int type = event.GetChangeType();
425 if ((type == wxFSW_EVENT_DELETE) || (type == wxFSW_EVENT_RENAME))
426 {
427 // If path is one of our watched dirs, we need to react to this
428 // otherwise there'll be asserts if later we try to remove it
429 wxString eventpath = event.GetPath().GetFullPath();
430 bool found(false);
431 for (size_t n = m_filesList->GetItemCount(); n > 0; --n)
432 {
433 wxString path, foo = m_filesList->GetItemText(n-1);
434 if ((!m_filesList->GetItemText(n-1).StartsWith("Dir: ", &path)) &&
435 (!m_filesList->GetItemText(n-1).StartsWith("Tree: ", &path)))
436 {
437 wxFAIL_MSG("Unexpected item in wxListView.");
438 }
439 if (path == eventpath)
440 {
441 if (type == wxFSW_EVENT_DELETE)
442 {
443 m_filesList->DeleteItem(n-1);
444 }
445 else
446 {
447 // At least in wxGTK, we'll never get here: renaming the top
448 // watched dir gives IN_MOVE_SELF and no new-name info.
449 // However I'll leave the code in case other platforms do
450 wxString newname = event.GetNewPath().GetFullPath();
451 if (newname.empty() ||
452 newname == event.GetPath().GetFullPath())
453 {
454 // Just in case either of these are possible...
455 wxLogTrace(wxTRACE_FSWATCHER,
456 "Invalid attempt to rename to %s", newname);
457 return;
458 }
459 wxString prefix =
460 m_filesList->GetItemText(n-1).StartsWith("Dir: ") ?
461 "Dir: " : "Tree: ";
462 m_filesList->SetItemText(n-1, prefix + newname);
463 }
464 found = true;
465 // Don't break: a filepath may have been added more than once
466 }
467 }
468
469 if (found)
470 {
471 wxString msg = wxString::Format(
472 "Your watched path %s has been deleted or renamed\n",
473 eventpath);
474 m_evtConsole->AppendText(msg);
475 }
476 }
477 }
478
479
480 static wxString GetFSWEventChangeTypeName(int changeType)
481 {
482 switch (changeType)
483 {
484 case wxFSW_EVENT_CREATE:
485 return "CREATE";
486 case wxFSW_EVENT_DELETE:
487 return "DELETE";
488 case wxFSW_EVENT_RENAME:
489 return "RENAME";
490 case wxFSW_EVENT_MODIFY:
491 return "MODIFY";
492 case wxFSW_EVENT_ACCESS:
493 return "ACCESS";
494 }
495
496 return "INVALID_TYPE";
497 }
498
499 void MyFrame::LogEvent(const wxFileSystemWatcherEvent& event)
500 {
501 wxString entry = wxString::Format(LOG_FORMAT + "\n",
502 GetFSWEventChangeTypeName(event.GetChangeType()),
503 event.GetPath().GetFullPath(),
504 event.GetNewPath().GetFullPath());
505 m_evtConsole->AppendText(entry);
506 }