fswatcher.cpp 18 KB

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