From 669df123648aaa6aeccc70206b5417bc48b4e9ae Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:26:50 +0200 Subject: 5.19 --- RealtimeSync/RealtimeSync.vcxproj | 26 +-- RealtimeSync/application.cpp | 27 +-- RealtimeSync/gui_generated.cpp | 6 + RealtimeSync/gui_generated.h | 2 +- RealtimeSync/main_dlg.cpp | 88 ++++---- RealtimeSync/main_dlg.h | 17 +- RealtimeSync/makefile | 2 +- RealtimeSync/monitor.cpp | 281 ++++++++++++++++++++++++ RealtimeSync/monitor.h | 38 ++++ RealtimeSync/tray_menu.cpp | 440 +++++++++++++++++++------------------- RealtimeSync/tray_menu.h | 10 +- RealtimeSync/watcher.cpp | 202 ----------------- RealtimeSync/watcher.h | 54 ----- RealtimeSync/xml_ffs.cpp | 14 +- RealtimeSync/xml_proc.h | 8 +- 15 files changed, 641 insertions(+), 574 deletions(-) create mode 100644 RealtimeSync/monitor.cpp create mode 100644 RealtimeSync/monitor.h delete mode 100644 RealtimeSync/watcher.cpp delete mode 100644 RealtimeSync/watcher.h (limited to 'RealtimeSync') diff --git a/RealtimeSync/RealtimeSync.vcxproj b/RealtimeSync/RealtimeSync.vcxproj index 487f2cce..08abd76f 100644 --- a/RealtimeSync/RealtimeSync.vcxproj +++ b/RealtimeSync/RealtimeSync.vcxproj @@ -29,27 +29,27 @@ Application true Unicode - Windows7.1SDK + v120_xp Application true Unicode - Windows7.1SDK + v120_xp Application false true Unicode - Windows7.1SDK + v120_xp Application false true Unicode - Windows7.1SDK + v120_xp @@ -99,7 +99,7 @@ Level4 Disabled _SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions) - C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll\mswud;.. + C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll\mswud;.. wx+/pch.h 4100;4512 true @@ -114,7 +114,7 @@ true $(OutDir)$(TargetName)$(TargetExt) wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;wxbase28ud_net.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies) - C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_debug_dll + C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_debug_dll C:\Program Files\C++\wxWidgets\include @@ -127,7 +127,7 @@ Level4 Disabled _SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;__WXDEBUG__;WXUSINGDLL;_DEBUG;%(PreprocessorDefinitions) - C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll\mswud;.. + C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll\mswud;.. wx+/pch.h 4100;4512 true @@ -144,7 +144,7 @@ true $(OutDir)$(TargetName)$(TargetExt) wxmsw28ud_adv.lib;wxmsw28ud_core.lib;wxbase28ud.lib;wxpngd.lib;wxzlibd.lib;wxbase28ud_net.lib;comctl32.lib;ws2_32.lib;Rpcrt4.lib;winmm.lib;%(AdditionalDependencies) - C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_debug_dll + C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_debug_dll @@ -160,7 +160,7 @@ MaxSpeed true _SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions) - C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib\mswu;.. + C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib\mswu;.. Speed 4100;4512 MultiThreaded @@ -174,7 +174,7 @@ true wxbase28u.lib;wxmsw28u_adv.lib;wxmsw28u_core.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) - C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x86_release_lib + C:\Program Files\C++\Boost\stage\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x86_release_lib C:\Program Files\C++\wxWidgets\include @@ -191,7 +191,7 @@ MaxSpeed true _SCL_SECURE_NO_WARNINGS;wxUSE_UNICODE;__WXMSW__;ZEN_WIN;WXINTL_NO_GETTEXT_MACRO;NDEBUG;%(PreprocessorDefinitions) - C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib\mswu;.. + C:\Program Files\C++\Boost;C:\Program Files\C++\wxWidgets\include;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib\mswu;.. Speed 4100;4512 MultiThreaded @@ -205,7 +205,7 @@ true wxmsw28u_adv.lib;wxmsw28u_core.lib;wxbase28u.lib;wxpng.lib;wxzlib.lib;wxbase28u_net.lib;comctl32.lib;ws2_32.lib;winmm.lib;Rpcrt4.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) - C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc10_x64_release_lib + C:\Program Files\C++\Boost\stage_x64\lib;C:\Program Files\C++\wxWidgets\lib\vc12_x64_release_lib C:\Program Files\C++\wxWidgets\include @@ -246,9 +246,9 @@ + - diff --git a/RealtimeSync/application.cpp b/RealtimeSync/application.cpp index a7aff305..af8a40a9 100644 --- a/RealtimeSync/application.cpp +++ b/RealtimeSync/application.cpp @@ -36,19 +36,20 @@ IMPLEMENT_APP(Application); namespace { +/* boost::thread::id mainThreadId = boost::this_thread::get_id(); void onTerminationRequested() { - std::wstring msg = boost::this_thread::get_id() == mainThreadId ? - L"Termination requested in main thread!\n\n" : - L"Termination requested in worker thread!\n\n"; - msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; +std::wstring msg = boost::this_thread::get_id() == mainThreadId ? + L"Termination requested in main thread!\n\n" : + L"Termination requested in worker thread!\n\n"; +msg += L"Please file a bug report at: http://sourceforge.net/projects/freefilesync"; - wxSafeShowMessage(_("An exception occurred"), msg); - std::abort(); +wxSafeShowMessage(_("An exception occurred"), msg); +std::abort(); } - +*/ #ifdef _MSC_VER void crtInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { assert(false); } #endif @@ -59,7 +60,7 @@ const wxEventType EVENT_ENTER_EVENT_LOOP = wxNewEventType(); bool Application::OnInit() { - std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads + //std::set_terminate(onTerminationRequested); //unlike wxWidgets uncaught exception handling, this works for all worker threads #ifdef ZEN_WIN #ifdef _MSC_VER @@ -107,12 +108,12 @@ void Application::onEnterEventLoop(wxEvent& event) } catch (const FileError& e) { - wxMessageBox(e.toString(), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(e.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); //continue! } //try to set config/batch-filename set by %1 parameter - std::vector commandArgs; + std::vector commandArgs; for (int i = 1; i < argc; ++i) { Zstring filename = toZ(argv[i]); @@ -125,14 +126,14 @@ void Application::onEnterEventLoop(wxEvent& event) filename += Zstr(".ffs_batch"); else { - wxMessageBox(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filename)), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return; } } - commandArgs.push_back(toWx(filename)); + commandArgs.push_back(filename); } - wxString cfgFilename; + Zstring cfgFilename; if (!commandArgs.empty()) cfgFilename = commandArgs[0]; diff --git a/RealtimeSync/gui_generated.cpp b/RealtimeSync/gui_generated.cpp index cf072240..659f7ea4 100644 --- a/RealtimeSync/gui_generated.cpp +++ b/RealtimeSync/gui_generated.cpp @@ -100,6 +100,8 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr bSizer1->Add( m_staticText7, 0, wxALL, 5 ); m_panelMainFolder = new wxPanel( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + m_panelMainFolder->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxFlexGridSizer* fgSizer1; fgSizer1 = new wxFlexGridSizer( 0, 2, 0, 0 ); fgSizer1->AddGrowableCol( 1 ); @@ -152,6 +154,8 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr m_scrolledWinFolders = new wxScrolledWindow( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); m_scrolledWinFolders->SetScrollRate( 10, 10 ); + m_scrolledWinFolders->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + bSizerFolders = new wxBoxSizer( wxVERTICAL ); @@ -243,6 +247,8 @@ MainDlgGenerated::~MainDlgGenerated() FolderGenerated::FolderGenerated( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style ) : wxPanel( parent, id, pos, size, style ) { + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) ); + wxBoxSizer* bSizer114; bSizer114 = new wxBoxSizer( wxHORIZONTAL ); diff --git a/RealtimeSync/gui_generated.h b/RealtimeSync/gui_generated.h index 2b42b271..aabb23fc 100644 --- a/RealtimeSync/gui_generated.h +++ b/RealtimeSync/gui_generated.h @@ -140,7 +140,7 @@ protected: public: - ErrorDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Error"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxRESIZE_BORDER ); + ErrorDlgGenerated( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Error"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); ~ErrorDlgGenerated(); }; diff --git a/RealtimeSync/main_dlg.cpp b/RealtimeSync/main_dlg.cpp index b750b110..b7a0aee1 100644 --- a/RealtimeSync/main_dlg.cpp +++ b/RealtimeSync/main_dlg.cpp @@ -9,8 +9,7 @@ #include #include #include -#include -#include +//#include #include #include #include @@ -18,7 +17,6 @@ #include #include #include -#include "watcher.h" #include "xml_proc.h" #include "tray_menu.h" #include "xml_ffs.h" @@ -44,7 +42,7 @@ private: }; -MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName) +MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) : MainDlgGenerated(dlg) { #ifdef ZEN_WIN @@ -73,20 +71,20 @@ MainDialog::MainDialog(wxDialog* dlg, const wxString& cfgFileName) //--------------------------- load config values ------------------------------------ xmlAccess::XmlRealConfig newConfig; - const wxString currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; + const Zstring currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; bool loadCfgSuccess = false; - if (!cfgFileName.empty() || wxFileExists(lastConfigFileName())) + if (!cfgFileName.empty() || fileExists(lastConfigFileName())) try { - rts::readRealOrBatchConfig(toZ(currentConfigFile), newConfig); //throw FfsXmlError + rts::readRealOrBatchConfig(currentConfigFile, newConfig); //throw FfsXmlError loadCfgSuccess = true; } catch (const xmlAccess::FfsXmlError& error) { if (error.getSeverity() == xmlAccess::FfsXmlError::WARNING) - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING, this); + wxMessageBox(error.toString(),L"RealtimeSync" + _("Warning"), wxOK | wxICON_WARNING, this); else - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } const bool startWatchingImmediately = loadCfgSuccess && !cfgFileName.empty(); @@ -127,11 +125,11 @@ MainDialog::~MainDialog() try //write config to XML { - writeRealConfig(currentCfg, toZ(lastConfigFileName())); //throw FfsXmlError + writeRealConfig(currentCfg, lastConfigFileName()); //throw FfsXmlError } catch (const xmlAccess::FfsXmlError& error) { - wxMessageBox(error.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString().c_str(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } } @@ -145,9 +143,9 @@ void MainDialog::onProcessAsyncTasks(wxEvent& event) } -const wxString& MainDialog::lastConfigFileName() +const Zstring& MainDialog::lastConfigFileName() { - static wxString instance = toWx(zen::getConfigDir()) + L"LastRun.ffs_real"; + static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_real"); return instance; } @@ -206,54 +204,59 @@ void MainDialog::OnStart(wxCommandEvent& event) break; } Show(); //don't show for EXIT_APP + Raise(); } void MainDialog::OnConfigSave(wxCommandEvent& event) { - wxString defaultFileName = currentConfigFileName.empty() ? L"Realtime.ffs_real" : currentConfigFileName; + Zstring defaultFileName = currentConfigFileName.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName; //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! - if (endsWith(defaultFileName, L".ffs_batch")) - replace(defaultFileName, L".ffs_batch", L".ffs_real", false); + if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + replace(defaultFileName, Zstr(".ffs_batch"), Zstr(".ffs_real"), false); - wxFileDialog filePicker(this, wxEmptyString, wxEmptyString, defaultFileName, + wxFileDialog filePicker(this, + wxEmptyString, + //OS X really needs dir/file separated like this: + utfCvrtTo(beforeLast(defaultFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + utfCvrtTo(afterLast (defaultFileName, FILE_NAME_SEPARATOR)), //default file; whole string if / not found wxString(L"RealtimeSync (*.ffs_real)|*.ffs_real") + L"|" +_("All files") + L" (*.*)|*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (filePicker.ShowModal() != wxID_OK) return; - const wxString newFileName = filePicker.GetPath(); + const Zstring newFileName = utfCvrtTo(filePicker.GetPath()); //write config to XML const xmlAccess::XmlRealConfig currentCfg = getConfiguration(); try { - writeRealConfig(currentCfg, toZ(newFileName)); //throw FfsXmlError + writeRealConfig(currentCfg, newFileName); //throw FfsXmlError setLastUsedConfig(newFileName); } catch (const xmlAccess::FfsXmlError& e) { - wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(e.toString().c_str(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); } } -void MainDialog::loadConfig(const wxString& filename) +void MainDialog::loadConfig(const Zstring& filename) { xmlAccess::XmlRealConfig newConfig; try { - rts::readRealOrBatchConfig(toZ(filename), newConfig); + rts::readRealOrBatchConfig(filename, newConfig); } catch (const xmlAccess::FfsXmlError& error) { if (error.getSeverity() == xmlAccess::FfsXmlError::WARNING) - wxMessageBox(error.toString(), _("Warning"), wxOK | wxICON_WARNING, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Warning"), wxOK | wxICON_WARNING, this); else { - wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR, this); + wxMessageBox(error.toString(), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR, this); return; } } @@ -263,7 +266,7 @@ void MainDialog::loadConfig(const wxString& filename) } -void MainDialog::setLastUsedConfig(const wxString& filename) +void MainDialog::setLastUsedConfig(const Zstring& filename) { //set title if (filename == lastConfigFileName()) @@ -273,7 +276,7 @@ void MainDialog::setLastUsedConfig(const wxString& filename) } else { - SetTitle(filename); + SetTitle(utfCvrtTo(filename)); currentConfigFileName = filename; } } @@ -281,11 +284,14 @@ void MainDialog::setLastUsedConfig(const wxString& filename) void MainDialog::OnConfigLoad(wxCommandEvent& event) { - wxFileDialog filePicker(this, wxEmptyString, wxEmptyString, wxEmptyString, + wxFileDialog filePicker(this, + wxEmptyString, + utfCvrtTo(beforeLast(currentConfigFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + wxEmptyString, wxString(L"RealtimeSync (*.ffs_real;*.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", wxFD_OPEN); if (filePicker.ShowModal() == wxID_OK) - loadConfig(filePicker.GetPath()); + loadConfig(utfCvrtTo(filePicker.GetPath())); } @@ -293,7 +299,7 @@ void MainDialog::onFilesDropped(FileDropEvent& event) { const auto& files = event.getFiles(); if (!files.empty()) - loadConfig(files[0]); + loadConfig(utfCvrtTo(files[0])); } @@ -307,14 +313,14 @@ void MainDialog::setConfiguration(const xmlAccess::XmlRealConfig& cfg) if (!cfg.directories.empty()) { //fill top folder - dirNameFirst->setName(*cfg.directories.begin()); + dirNameFirst->setName(utfCvrtTo(*cfg.directories.begin())); //fill additional folders - addFolder(std::vector(cfg.directories.begin() + 1, cfg.directories.end())); + addFolder(std::vector(cfg.directories.begin() + 1, cfg.directories.end())); } //fill commandline - m_textCtrlCommand->SetValue(cfg.commandline); + m_textCtrlCommand->SetValue(utfCvrtTo(cfg.commandline)); //set delay m_spinCtrlDelay->SetValue(static_cast(cfg.delay)); @@ -325,11 +331,11 @@ xmlAccess::XmlRealConfig MainDialog::getConfiguration() { xmlAccess::XmlRealConfig output; - output.directories.push_back(dirNameFirst->getName()); - for (std::vector::const_iterator i = dirNamesExtra.begin(); i != dirNamesExtra.end(); ++i) - output.directories.push_back((*i)->getName()); + output.directories.push_back(utfCvrtTo(dirNameFirst->getName())); + for (auto it = dirNamesExtra.begin(); it != dirNamesExtra.end(); ++it) + output.directories.push_back(utfCvrtTo((*it)->getName())); - output.commandline = m_textCtrlCommand->GetValue(); + output.commandline = utfCvrtTo(m_textCtrlCommand->GetValue()); output.delay = m_spinCtrlDelay->GetValue(); return output; @@ -338,12 +344,12 @@ xmlAccess::XmlRealConfig MainDialog::getConfiguration() void MainDialog::OnAddFolder(wxCommandEvent& event) { - const wxString topFolder = dirNameFirst->getName(); + const Zstring topFolder = utfCvrtTo(dirNameFirst->getName()); //clear existing top folder first dirNameFirst->setName(wxString()); - std::vector newFolders; + std::vector newFolders; newFolders.push_back(topFolder); addFolder(newFolders, true); //add pair in front of additonal pairs @@ -383,7 +389,7 @@ static const size_t MAX_ADD_FOLDERS = 6; #endif -void MainDialog::addFolder(const std::vector& newFolders, bool addFront) +void MainDialog::addFolder(const std::vector& newFolders, bool addFront) { if (newFolders.size() == 0) return; @@ -391,7 +397,7 @@ void MainDialog::addFolder(const std::vector& newFolders, bool addFron wxWindowUpdateLocker dummy(this); //avoid display distortion int folderHeight = 0; - for (std::vector::const_iterator i = newFolders.begin(); i != newFolders.end(); ++i) + for (auto it = newFolders.begin(); it != newFolders.end(); ++it) { //add new folder pair DirectoryPanel* newFolder = new DirectoryPanel(m_scrolledWinFolders); @@ -415,7 +421,7 @@ void MainDialog::addFolder(const std::vector& newFolders, bool addFron newFolder->m_bpButtonRemoveFolder->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolder), nullptr, this ); //insert directory name - newFolder->setName(*i); + newFolder->setName(utfCvrtTo(*it)); } //set size of scrolled window diff --git a/RealtimeSync/main_dlg.h b/RealtimeSync/main_dlg.h index 5a028f3a..2a1fb03e 100644 --- a/RealtimeSync/main_dlg.h +++ b/RealtimeSync/main_dlg.h @@ -10,9 +10,10 @@ #include "gui_generated.h" #include #include +#include +#include #include #include "../ui/dir_name.h" -#include namespace xmlAccess { @@ -24,11 +25,11 @@ class DirectoryPanel; class MainDialog: public MainDlgGenerated { public: - MainDialog(wxDialog* dlg, const wxString& cfgFileName); + MainDialog(wxDialog* dlg, const Zstring& cfgFileName); ~MainDialog(); private: - void loadConfig(const wxString& filename); + void loadConfig(const Zstring& filename); virtual void OnClose (wxCloseEvent& event) { Destroy(); } virtual void OnShowHelp (wxCommandEvent& event); @@ -45,21 +46,21 @@ private: void setConfiguration(const xmlAccess::XmlRealConfig& cfg); xmlAccess::XmlRealConfig getConfiguration(); - void setLastUsedConfig(const wxString& filename); + void setLastUsedConfig(const Zstring& filename); void layoutAsync(); //call Layout() asynchronously - void addFolder(const wxString& dirname, bool addFront = false); - void addFolder(const std::vector& newFolders, bool addFront = false); + //void addFolder(const Zstring& dirname, bool addFront = false); + void addFolder(const std::vector& newFolders, bool addFront = false); void removeAddFolder(size_t pos); void clearAddFolders(); - static const wxString& lastConfigFileName(); + static const Zstring& lastConfigFileName(); std::unique_ptr> dirNameFirst; std::vector dirNamesExtra; //additional pairs to the standard pair - wxString currentConfigFileName; + Zstring currentConfigFileName; void onProcessAsyncTasks(wxEvent& event); diff --git a/RealtimeSync/makefile b/RealtimeSync/makefile index 865d73a1..d84c425f 100644 --- a/RealtimeSync/makefile +++ b/RealtimeSync/makefile @@ -58,7 +58,7 @@ CPP_LIST+=gui_generated.cpp CPP_LIST+=main_dlg.cpp CPP_LIST+=resources.cpp CPP_LIST+=tray_menu.cpp -CPP_LIST+=watcher.cpp +CPP_LIST+=monitor.cpp CPP_LIST+=xml_ffs.cpp CPP_LIST+=xml_proc.cpp CPP_LIST+=../structures.cpp diff --git a/RealtimeSync/monitor.cpp b/RealtimeSync/monitor.cpp new file mode 100644 index 00000000..88536281 --- /dev/null +++ b/RealtimeSync/monitor.cpp @@ -0,0 +1,281 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "monitor.h" +#include +#include +#include +#include +#include +#include +#include +#include "../lib/resolve_path.h" +//#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header +//#include "../library/lock_holder.h" //LOCK_FILE_ENDING + +using namespace zen; + + +namespace +{ +const int CHECK_DIR_INTERVAL = 1; //unit: [s] + + +std::vector getFormattedDirs(const std::vector& dirs) //throw FileError +{ + std::set tmp; //make unique + + std::transform(dirs.begin(), dirs.end(), std::inserter(tmp, tmp.end()), + [](const Zstring& dirnameNonFmt) { return getFormattedDirectoryName(dirnameNonFmt); }); + + return std::vector(tmp.begin(), tmp.end()); +} + + +//wait until changes are detected or if a directory is not available (anymore) +struct WaitResult +{ + enum ChangeType + { + CHANGE_DETECTED, + CHANGE_DIR_MISSING + }; + + WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} + WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {} + + ChangeType type; + zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory + Zstring dirname_; //for type == CHANGE_DIR_MISSING +}; + + +WaitResult waitForChanges(const std::vector& dirNamesNonFmt, //throw FileError + const std::function& onRefreshGui) //bool: readyForSync +{ + const std::vector dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError + if (dirNamesFmt.empty()) //pathological case, but we have to check else this function will wait endlessly + throw zen::FileError(_("A folder input field is empty.")); //should have been checked by caller! + + //detect when volumes are removed/are not available anymore + std::vector>> watches; + + for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it) + { + const Zstring& dirnameFmt = *it; + try + { + //a non-existent network path may block, so check existence asynchronously! + auto ftDirExists = async([=] { return zen::dirExists(dirnameFmt); }); + //we need to check dirExists(), not somethingExists(): it's not clear if DirWatcher detects a type clash (file instead of directory!) + while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2))) + onRefreshGui(false); //may throw! + if (!ftDirExists.get()) + return WaitResult(dirnameFmt); + + watches.push_back(std::make_pair(dirnameFmt, std::make_shared(dirnameFmt))); //throw FileError + } + catch (FileError&) + { + if (!somethingExists(dirnameFmt)) //a benign(?) race condition with FileError + return WaitResult(dirnameFmt); + throw; + } + } + + const std::int64_t TICKS_DIR_CHECK_INTERVAL = CHECK_DIR_INTERVAL * ticksPerSec(); //0 on error + TickVal lastCheck = getTicks(); //0 on error + while (true) + { + const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice + { + const TickVal now = getTicks(); //0 on error + if (dist(lastCheck, now) >= TICKS_DIR_CHECK_INTERVAL) + { + lastCheck = now; + return true; + } + return false; + }(); + + + for (auto it = watches.begin(); it != watches.end(); ++it) + { + const Zstring& dirname = it->first; + DirWatcher& watcher = *(it->second); + + //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! + if (checkDirExistNow) + if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED -> somethingExists() is NOT sufficient here! + return WaitResult(dirname); + try + { + std::vector changedItems = watcher.getChanges([&] { onRefreshGui(false); /*may throw!*/ }); //throw FileError + + //remove to be ignored changes + vector_remove_if(changedItems, [](const DirWatcher::Entry& e) + { + return endsWith(e.filename_, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock + endsWith(e.filename_, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db + //no need to ignore temporal recycle bin directory: this must be caused by a file deletion anyway + }); + + if (!changedItems.empty()) + { + /* + std::for_each(changedItems.begin(), changedItems.end(), + [](const Zstring& fn) { wxMessageBox(toWx(fn));}); + */ + return WaitResult(changedItems[0]); //directory change detected + } + + } + catch (FileError&) + { + if (!somethingExists(dirname)) //a benign(?) race condition with FileError + return WaitResult(dirname); + throw; + } + } + + boost::this_thread::sleep(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); + onRefreshGui(true); //throw ?: may start sync at this presumably idle time + } +} + + +//wait until all directories become available (again) + logs in network share +void waitForMissingDirs(const std::vector& dirNamesNonFmt, //throw FileError + const std::function& onRefreshGui) //Zstring: the directory that is currently being waited for +{ + while (true) + { + //support specifying volume by name => call getFormattedDirectoryName() repeatedly + const std::vector& dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError + + bool allExisting = true; + for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it) + { + const Zstring dirnameFmt = *it; + auto ftDirExisting = async([=]() -> bool + { +#ifdef ZEN_WIN + //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!? + loginNetworkShare(dirnameFmt, false); //login networks shares, no PW prompt -> is this really RTS's job? +#endif + //2. check dir existence + return zen::dirExists(dirnameFmt); + }); + while (!ftDirExisting.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2))) + onRefreshGui(dirnameFmt); //may throw! + + if (!ftDirExisting.get()) + { + allExisting = false; + //wait some time... + const int refreshInterval = rts::UI_UPDATE_INTERVAL / 2; + assert_static(CHECK_DIR_INTERVAL * 1000 % refreshInterval == 0); + for (int i = 0; i < CHECK_DIR_INTERVAL * 1000 / refreshInterval; ++i) + { + onRefreshGui(dirnameFmt); //may throw! + boost::this_thread::sleep(boost::posix_time::milliseconds(refreshInterval)); + } + break; + } + } + if (allExisting) + return; + } +} + + +inline +wxString toString(DirWatcher::ActionType type) +{ + switch (type) + { + case DirWatcher::ACTION_CREATE: + return L"CREATE"; + case DirWatcher::ACTION_UPDATE: + return L"UPDATE"; + case DirWatcher::ACTION_DELETE: + return L"DELETE"; + } + return L"ERROR"; +} + +struct ExecCommandNowException {}; +} + + +void rts::monitorDirectories(const std::vector& dirNamesNonFmt, unsigned int delay, rts::MonitorCallback& callback) +{ + if (dirNamesNonFmt.empty()) + { + assert(false); + return; + } + + auto execMonitoring = [&] //throw FileError + { + callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); + waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError + callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); + + //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) + time_t nextExecDate = std::time(nullptr) + delay; + + while (true) //loop over command invocations + { + DirWatcher::Entry lastChangeDetected; + try + { + while (true) //loop over detected changes + { + //wait for changes (and for all directories to become available) + WaitResult res = waitForChanges(dirNamesNonFmt, [&](bool readyForSync) //throw FileError, ExecCommandNowException + { + if (readyForSync) + if (nextExecDate <= std::time(nullptr)) + throw ExecCommandNowException(); //abort wait and start sync + callback.requestUiRefresh(); + }); + switch (res.type) + { + case WaitResult::CHANGE_DIR_MISSING: //don't execute the command before all directories are available! + callback.setPhase(MonitorCallback::MONITOR_PHASE_WAITING); + waitForMissingDirs(dirNamesNonFmt, [&](const Zstring& dirname) { callback.requestUiRefresh(); }); //throw FileError + callback.setPhase(MonitorCallback::MONITOR_PHASE_ACTIVE); + break; + + case WaitResult::CHANGE_DETECTED: + lastChangeDetected = res.changedItem_; + break; + } + nextExecDate = std::time(nullptr) + delay; + } + } + catch (ExecCommandNowException&) {} + + ::wxSetEnv(L"change_path", utfCvrtTo(lastChangeDetected.filename_)); //some way to output what file changed to the user + ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); // + + //execute command + callback.executeExternalCommand(); + nextExecDate = std::numeric_limits::max(); + } + }; + + while (true) + try + { + execMonitoring(); //throw FileError + } + catch (const zen::FileError& e) + { + callback.reportError(e.toString()); + } +} diff --git a/RealtimeSync/monitor.h b/RealtimeSync/monitor.h new file mode 100644 index 00000000..0b9dfbc0 --- /dev/null +++ b/RealtimeSync/monitor.h @@ -0,0 +1,38 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef MONITOR_HEADER_345087425834253425 +#define MONITOR_HEADER_345087425834253425 + +#include +#include + +namespace rts +{ +const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss + + +struct MonitorCallback +{ + virtual ~MonitorCallback() {} + + enum WatchPhase + { + MONITOR_PHASE_ACTIVE, + MONITOR_PHASE_WAITING, + }; + virtual void setPhase(WatchPhase mode) = 0; + virtual void executeExternalCommand() = 0; + virtual void requestUiRefresh() = 0; + virtual void reportError(const std::wstring& msg) = 0; //automatically retries after return! +}; +void monitorDirectories(const std::vector& dirNamesNonFmt, + //non-formatted dirnames that yet require call to getFormattedDirectoryName(); empty directories must be checked by caller! + unsigned int delay, + MonitorCallback& callback); +} + +#endif //MONITOR_HEADER_345087425834253425 diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp index 676904f1..33758ad2 100644 --- a/RealtimeSync/tray_menu.cpp +++ b/RealtimeSync/tray_menu.cpp @@ -5,28 +5,20 @@ // ************************************************************************** #include "tray_menu.h" -#include -#include -#include -#include -#include #include +#include +#include #include #include -#include +//#include #include #include -#include #include -#include -#include -#include -#include #include //Linux needs this -#include +#include #include "resources.h" #include "gui_generated.h" -#include "watcher.h" +#include "monitor.h" #include "../lib/resolve_path.h" using namespace rts; @@ -35,67 +27,149 @@ using namespace zen; namespace { -struct AbortCallback //never throw exceptions through a C-Layer (GUI)! +const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; +TickVal lastExec = getTicks(); + +bool updateUiIsAllowed() +{ + const TickVal now = getTicks(); //0 on error + if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary + { + lastExec = now; + return true; + } + return false; +} + + +enum TrayMode { - virtual ~AbortCallback() {} - virtual void requestResume() = 0; - virtual void requestAbort() = 0; + TRAY_MODE_ACTIVE, + TRAY_MODE_WAITING, + TRAY_MODE_ERROR, }; -//RtsTrayIcon is a dumb class whose sole purpose is to enable wxWidgets deferred deletion -class RtsTrayIconRaw : public wxTaskBarIcon +class TrayIconObject : public wxTaskBarIcon { public: - RtsTrayIconRaw(AbortCallback& abortCb) : abortCb_(&abortCb) + TrayIconObject(const wxString& jobname) : + resumeRequested(false), + abortRequested(false), + showErrorMsgRequested(false), + mode(TRAY_MODE_ACTIVE), + iconFlashStatusLast(false), + jobName_(jobname), +#if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X + trayBmp(getResourceImage(L"RTS_tray_16x16")) //use a 16x16 bitmap +#elif defined ZEN_LINUX + trayBmp(getResourceImage(L"RTS_tray_24x24")) //use a 24x24 bitmap for perfect fit +#endif { - Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(RtsTrayIconRaw::OnDoubleClick), nullptr, this); + Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TrayIconObject::OnDoubleClick), nullptr, this); + setMode(mode); } - void dontCallBackAnymore() { abortCb_ = nullptr; } //call before tray icon is marked for deferred deletion + //require polling: + bool resumeIsRequested() const { return resumeRequested; } + bool abortIsRequested () const { return abortRequested; } + + //during TRAY_MODE_ERROR those two functions are available: + void clearShowErrorRequested() { assert(mode == TRAY_MODE_ERROR); showErrorMsgRequested = false; } + bool getShowErrorRequested() const { assert(mode == TRAY_MODE_ERROR); return showErrorMsgRequested; } + + void setMode(TrayMode m) + { + mode = m; + timer.Stop(); + timer.Disconnect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); + switch (m) + { + case TRAY_MODE_ACTIVE: + setTrayIcon(trayBmp, _("Directory monitoring active")); + break; + + case TRAY_MODE_WAITING: + setTrayIcon(greyScale(trayBmp), _("Waiting until all directories are available...")); + break; + + case TRAY_MODE_ERROR: + timer.Connect(wxEVT_TIMER, wxEventHandler(TrayIconObject::OnErrorFlashIcon), nullptr, this); + timer.Start(500); //timer interval in [ms] + break; + } + } private: + void OnErrorFlashIcon(wxEvent& event) + { + iconFlashStatusLast = !iconFlashStatusLast; + setTrayIcon(iconFlashStatusLast ? trayBmp : greyScale(trayBmp), _("Error")); + } + + void setTrayIcon(const wxBitmap& bmp, const wxString& statusTxt) + { + wxIcon realtimeIcon; + realtimeIcon.CopyFromBitmap(bmp); + wxString tooltip = L"RealtimeSync\n" + statusTxt; + if (!jobName_.empty()) + tooltip += L"\n\"" + jobName_ + L"\""; + SetIcon(realtimeIcon, tooltip); + } + enum Selection { - CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of Zero does not work under Mac" + CONTEXT_RESTORE = 1, //wxWidgets: "A MenuItem ID of zero does not work under Mac" + CONTEXT_SHOW_ERROR, CONTEXT_ABORT = wxID_EXIT, CONTEXT_ABOUT = wxID_ABOUT }; virtual wxMenu* CreatePopupMenu() { - if (!abortCb_) - return nullptr; - wxMenu* contextMenu = new wxMenu; - contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + switch (mode) + { + case TRAY_MODE_ACTIVE: + case TRAY_MODE_WAITING: + contextMenu->Append(CONTEXT_RESTORE, _("&Restore")); + break; + case TRAY_MODE_ERROR: + contextMenu->Append(CONTEXT_SHOW_ERROR, _("&Show error")); + break; + } contextMenu->Append(CONTEXT_ABOUT, _("&About")); contextMenu->AppendSeparator(); contextMenu->Append(CONTEXT_ABORT, _("&Exit")); //event handling - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RtsTrayIconRaw::OnContextMenuSelection), nullptr, this); + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TrayIconObject::OnContextMenuSelection), nullptr, this); return contextMenu; //ownership transferred to caller } void OnContextMenuSelection(wxCommandEvent& event) { - if (!abortCb_) - return; - switch (static_cast(event.GetId())) { case CONTEXT_ABORT: - abortCb_->requestAbort(); + abortRequested = true; break; case CONTEXT_RESTORE: - abortCb_->requestResume(); + resumeRequested = true; + break; + + case CONTEXT_SHOW_ERROR: + showErrorMsgRequested = true; break; case CONTEXT_ABOUT: { - //build information + //ATTENTION: the modal dialog below does NOT disable all GUI input, e.g. user may still double-click on tray icon + //no crash in this context, but the double-click is remembered and executed after the modal dialog quits + SetEvtHandlerEnabled(false); + ZEN_ON_SCOPE_EXIT(SetEvtHandlerEnabled(true)); + wxString build = __TDATE__; #if wxUSE_UNICODE build += L" - Unicode"; @@ -103,7 +177,6 @@ private: build += L" - ANSI"; #endif //wxUSE_UNICODE - //compile time info about 32/64-bit build if (zen::is64BitBuild) build += L" x64"; else @@ -118,136 +191,86 @@ private: void OnDoubleClick(wxCommandEvent& event) { - if (abortCb_) - abortCb_->requestResume(); - } - - AbortCallback* abortCb_; -}; - - -class TrayIconHolder -{ -public: - TrayIconHolder(const wxString& jobname, AbortCallback& abortCb) : - jobName_(jobname), - trayMenu(new RtsTrayIconRaw(abortCb)) - { - showIconActive(); + switch (mode) + { + case TRAY_MODE_ACTIVE: + case TRAY_MODE_WAITING: + resumeRequested = true; //never throw exceptions through a C-Layer call stack (GUI)! + break; + case TRAY_MODE_ERROR: + showErrorMsgRequested = true; + break; + } } - ~TrayIconHolder() - { - trayMenu->RemoveIcon(); //(try to) hide icon until final deletion takes place - trayMenu->dontCallBackAnymore(); + bool resumeRequested; + bool abortRequested; + bool showErrorMsgRequested; - //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) - if (!wxPendingDelete.Member(trayMenu)) - wxPendingDelete.Append(trayMenu); - } + TrayMode mode; - void doUiRefreshNow() - { - wxTheApp->Yield(); - } //yield is UI-layer which is represented by this tray icon + bool iconFlashStatusLast; //flash try icon for TRAY_MODE_ERROR + wxTimer timer; // - void showIconActive() - { - wxIcon realtimeIcon; -#if defined ZEN_WIN || defined ZEN_MAC //16x16 seems to be the only size that is shown correctly on OS X - realtimeIcon.CopyFromBitmap(getResourceImage(L"RTS_tray_16x16")); //use a 16x16 bitmap -#elif defined ZEN_LINUX - realtimeIcon.CopyFromBitmap(getResourceImage(L"RTS_tray_24x24")); //use a 24x24 bitmap for perfect fit -#endif - wxString tooltip = L"RealtimeSync"; - if (!jobName_.empty()) - tooltip += L"\n\"" + jobName_ + L"\""; - trayMenu->SetIcon(realtimeIcon, tooltip); - } - - void showIconWaiting() - { - wxIcon realtimeIcon; -#if defined ZEN_WIN || defined ZEN_MAC - realtimeIcon.CopyFromBitmap(greyScale(getResourceImage(L"RTS_tray_16x16"))); -#elif defined ZEN_LINUX - realtimeIcon.CopyFromBitmap(greyScale(getResourceImage(L"RTS_tray_24x24"))); -#endif - wxString tooltip = _("Waiting for missing directories..."); - if (!jobName_.empty()) - tooltip += L"\n\"" + jobName_ + L"\""; - trayMenu->SetIcon(realtimeIcon, tooltip); - } - -private: const wxString jobName_; //RTS job name, may be empty - RtsTrayIconRaw* trayMenu; + const wxBitmap trayBmp; }; -//############################################################################################################## - -struct AbortMonitoring//exception class +struct AbortMonitoring //exception class { AbortMonitoring(AbortReason reasonCode) : reasonCode_(reasonCode) {} AbortReason reasonCode_; }; -class StartSyncNowException {}; - -//############################################################################################################## -class WaitCallbackImpl : public rts::WaitCallback, private AbortCallback +//=> don't derive from wxEvtHandler or any other wxWidgets object unless instance is safely deleted (deferred) during idle event!!tray_icon.h +class TrayIconHolder { public: - WaitCallbackImpl(const wxString& jobname) : - trayIcon(jobname, *this), - nextSyncStart_(std::numeric_limits::max()), - resumeRequested(false), - abortRequested(false) {} - - void notifyAllDirectoriesExist() { trayIcon.showIconActive(); } - void notifyDirectoryMissing () { trayIcon.showIconWaiting(); } + TrayIconHolder(const wxString& jobname) : + trayObj(new TrayIconObject(jobname)) {} - void scheduleNextSync(long nextSyncStart) { nextSyncStart_ = nextSyncStart; } - void clearSchedule() { nextSyncStart_ = std::numeric_limits::max(); } + ~TrayIconHolder() + { + //harmonize with tray_icon.cpp!!! + trayObj->RemoveIcon(); + //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking) + wxPendingDelete.Append(trayObj); + } - //implement WaitCallback - virtual void requestUiRefresh(bool readyForSync) //throw StartSyncNowException, AbortMonitoring + void doUiRefreshNow() //throw AbortMonitoring { - if (resumeRequested) + wxTheApp->Yield(); //yield is UI-layer which is represented by this tray icon + + //advantage of polling vs callbacks: we can throw exceptions! + if (trayObj->resumeIsRequested()) throw AbortMonitoring(SHOW_GUI); - if (abortRequested) + if (trayObj->abortIsRequested()) throw AbortMonitoring(EXIT_APP); + } - if (readyForSync) - if (nextSyncStart_ <= wxGetLocalTime()) - throw StartSyncNowException(); //abort wait and start sync + void setMode(TrayMode m) { trayObj->setMode(m); } - if (updateUiIsAllowed()) - trayIcon.doUiRefreshNow(); - } + bool getShowErrorRequested() const { return trayObj->getShowErrorRequested(); } + void clearShowErrorRequested() { trayObj->clearShowErrorRequested(); } private: - //implement AbortCallback: used from C-GUI call stack - virtual void requestResume() { resumeRequested = true; } - virtual void requestAbort () { abortRequested = true; } - - TrayIconHolder trayIcon; - long nextSyncStart_; - bool resumeRequested; - bool abortRequested; + TrayIconObject* trayObj; }; +//############################################################################################################## - +//#define ERROR_DLG_ENABLE_TIMEOUT class ErrorDlgWithTimeout : public ErrorDlgGenerated { public: ErrorDlgWithTimeout(wxWindow* parent, const wxString& messageText) : - ErrorDlgGenerated(parent), - secondsLeft(15) //give user some time to read msg!? + ErrorDlgGenerated(parent) +#ifdef ERROR_DLG_ENABLE_TIMEOUT + , secondsLeft(15) //give user some time to read msg!? +#endif { #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" @@ -257,11 +280,12 @@ public: m_bitmap10->SetBitmap(getResourceImage(L"msg_error")); m_textCtrl8->SetValue(messageText); +#ifdef ERROR_DLG_ENABLE_TIMEOUT //count down X seconds then automatically press "retry" timer.Connect(wxEVT_TIMER, wxEventHandler(ErrorDlgWithTimeout::OnTimerEvent), nullptr, this); timer.Start(1000); //timer interval in ms updateButtonLabel(); - +#endif Fit(); //child-element widths have changed: image was set m_buttonRetry->SetFocus(); } @@ -273,6 +297,7 @@ public: }; private: +#ifdef ERROR_DLG_ENABLE_TIMEOUT void OnTimerEvent(wxEvent& event) { if (secondsLeft <= 0) @@ -289,20 +314,23 @@ private: m_buttonRetry->SetLabel(_("&Retry") + L" (" + replaceCpy(_P("1 sec", "%x sec", secondsLeft), L"%x", numberTo(secondsLeft)) + L")"); Layout(); } +#endif void OnClose(wxCloseEvent& event) { EndModal(BUTTON_ABORT); } void OnRetry(wxCommandEvent& event) { EndModal(BUTTON_RETRY); } void OnAbort(wxCommandEvent& event) { EndModal(BUTTON_ABORT); } +#ifdef ERROR_DLG_ENABLE_TIMEOUT int secondsLeft; wxTimer timer; +#endif }; -bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or user selected "retry", else abort +bool reportErrorTimeout(const std::wstring& msg) //return true: "retry"; false: "abort" { ErrorDlgWithTimeout errorDlg(nullptr, msg); - //errorDlg.Raise(); -> don't steal focus every X seconds + errorDlg.Raise(); switch (static_cast(errorDlg.ShowModal())) { case ErrorDlgWithTimeout::BUTTON_RETRY: @@ -312,120 +340,90 @@ bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or use } return false; } - - -inline -wxString toString(DirWatcher::ActionType type) -{ - switch (type) - { - case DirWatcher::ACTION_CREATE: - return L"CREATE"; - case DirWatcher::ACTION_UPDATE: - return L"UPDATE"; - case DirWatcher::ACTION_DELETE: - return L"DELETE"; - } - return L"ERROR"; -} } -/* -Data Flow: ----------- - -TrayIconHolder (GUI output) - /|\ - | -WaitCallbackImpl - /|\ - | -startDirectoryMonitor() (wire dir-changes and execution of commandline) -*/ - rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname) { - std::vector dirList = toZ(config.directories); - vector_remove_if(dirList, [](Zstring str) -> bool { trim(str); return str.empty(); }); //remove empty entries WITHOUT formatting dirList yet! + std::vector dirNamesNonFmt = config.directories; + vector_remove_if(dirNamesNonFmt, [](Zstring str) -> bool { trim(str); return str.empty(); }); //remove empty entries WITHOUT formatting paths yet! - if (dirList.empty()) + if (dirNamesNonFmt.empty()) { - wxMessageBox(_("A folder input field is empty."), _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("A folder input field is empty."), L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return SHOW_GUI; } - wxString cmdLine = config.commandline; + Zstring cmdLine = config.commandline; trim(cmdLine); if (cmdLine.empty()) { - wxMessageBox(_("Invalid command line:") + L" \"\"", _("Error"), wxOK | wxICON_ERROR); + wxMessageBox(_("Invalid command line:") + L" \"\"", L"RealtimeSync" + _("Error"), wxOK | wxICON_ERROR); return SHOW_GUI; } - try + struct MonitorCallbackImpl : public MonitorCallback { - DirWatcher::Entry lastChangeDetected; - WaitCallbackImpl callback(jobname); + MonitorCallbackImpl(const wxString& jobname, + const Zstring& cmdLine) : trayIcon(jobname), cmdLine_(cmdLine) {} + + virtual void setPhase(WatchPhase mode) + { + switch (mode) + { + case MONITOR_PHASE_ACTIVE: + trayIcon.setMode(TRAY_MODE_ACTIVE); + break; + case MONITOR_PHASE_WAITING: + trayIcon.setMode(TRAY_MODE_WAITING); + break; + } + } - auto execMonitoring = [&] //throw FileError, AbortMonitoring + virtual void executeExternalCommand() { - callback.notifyDirectoryMissing(); - callback.clearSchedule(); - waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring - callback.notifyAllDirectoriesExist(); + auto cmdLineExp = expandMacros(cmdLine_); + zen::shellExecute(cmdLineExp, zen::EXEC_TYPE_SYNC); + } - //schedule initial execution (*after* all directories have arrived, which could take some time which we don't want to include) - callback.scheduleNextSync(wxGetLocalTime() + static_cast(config.delay)); + virtual void requestUiRefresh() + { + if (updateUiIsAllowed()) + trayIcon.doUiRefreshNow(); //throw AbortMonitoring + } + + virtual void reportError(const std::wstring& msg) + { + trayIcon.setMode(TRAY_MODE_ERROR); + trayIcon.clearShowErrorRequested(); - while (true) + //wait for some time, then return to retry + assert_static(15 * 1000 % UI_UPDATE_INTERVAL == 0); + for (int i = 0; i < 15 * 1000 / UI_UPDATE_INTERVAL; ++i) { - try + trayIcon.doUiRefreshNow(); //throw AbortMonitoring + + if (trayIcon.getShowErrorRequested()) { - while (true) - { - //wait for changes (and for all directories to become available) - WaitResult res = waitForChanges(dirList, callback); //throw FileError, StartSyncNowException, AbortMonitoring - switch (res.type) - { - case CHANGE_DIR_MISSING: //don't execute the commandline before all directories are available! - callback.notifyDirectoryMissing(); - callback.clearSchedule(); - waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring - callback.notifyAllDirectoriesExist(); - break; - - case CHANGE_DETECTED: - lastChangeDetected = res.changedItem_; - break; - } - callback.scheduleNextSync(wxGetLocalTime() + static_cast(config.delay)); - } + if (reportErrorTimeout(msg)) //return true: "retry"; false: "abort" + return; + else + throw AbortMonitoring(SHOW_GUI); } - catch (StartSyncNowException&) {} - - ::wxSetEnv(L"change_path", utfCvrtTo(lastChangeDetected.filename_)); //some way to output what file changed to the user - ::wxSetEnv(L"change_action", toString(lastChangeDetected.action_)); // - lastChangeDetected = DirWatcher::Entry(); //make sure old name is not shown again after a directory reappears - - //execute command - auto cmdLineExp = expandMacros(utfCvrtTo(cmdLine)); - zen::shellExecute(cmdLineExp, zen::EXEC_TYPE_SYNC); - callback.clearSchedule(); + boost::this_thread::sleep(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL)); } - }; + } - while (true) - try - { - execMonitoring(); //throw FileError, AbortMonitoring - } - catch (const zen::FileError& e) - { - if (!reportErrorTimeout(e.toString())) //return true if timeout or user selected "retry", else abort - return SHOW_GUI; - } + TrayIconHolder trayIcon; + const Zstring cmdLine_; + } cb(jobname, cmdLine); + + try + { + monitorDirectories(dirNamesNonFmt, config.delay, cb); //cb: throw AbortMonitoring + assert(false); + return SHOW_GUI; } catch (const AbortMonitoring& ab) { diff --git a/RealtimeSync/tray_menu.h b/RealtimeSync/tray_menu.h index 006dae82..1f71a017 100644 --- a/RealtimeSync/tray_menu.h +++ b/RealtimeSync/tray_menu.h @@ -4,13 +4,12 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef TRAYMENU_H_INCLUDED -#define TRAYMENU_H_INCLUDED +#ifndef TRAY_583967857420987534253245 +#define TRAY_583967857420987534253245 -#include "watcher.h" +#include #include "xml_proc.h" - namespace rts { enum AbortReason @@ -21,5 +20,4 @@ enum AbortReason AbortReason startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname); //jobname may be empty } - -#endif // TRAYMENU_H_INCLUDED +#endif //TRAY_583967857420987534253245 diff --git a/RealtimeSync/watcher.cpp b/RealtimeSync/watcher.cpp deleted file mode 100644 index 6ef5e924..00000000 --- a/RealtimeSync/watcher.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#include "watcher.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "../lib/resolve_path.h" -//#include "../library/db_file.h" //SYNC_DB_FILE_ENDING -> complete file too much of a dependency; file ending too little to decouple into single header -//#include "../library/lock_holder.h" //LOCK_FILE_ENDING - -using namespace zen; - - -namespace -{ -const std::int64_t TICKS_UPDATE_INTERVAL = rts::UI_UPDATE_INTERVAL* ticksPerSec() / 1000; -TickVal lastExec = getTicks(); -}; - -bool rts::updateUiIsAllowed() -{ - const TickVal now = getTicks(); //0 on error - if (dist(lastExec, now) >= TICKS_UPDATE_INTERVAL) //perform ui updates not more often than necessary - { - lastExec = now; - return true; - } - return false; -} - - -namespace -{ -const int CHECK_DIR_INTERVAL = 1; //unit: [s] - - -std::vector getFormattedDirs(const std::vector& dirs) //throw FileError -{ - std::set tmp; //make unique - - std::transform(dirs.begin(), dirs.end(), std::inserter(tmp, tmp.end()), - [](const Zstring& dirnameNonFmt) { return getFormattedDirectoryName(dirnameNonFmt); }); - - return std::vector(tmp.begin(), tmp.end()); -} -} - - -rts::WaitResult rts::waitForChanges(const std::vector& dirNamesNonFmt, WaitCallback& statusHandler) //throw FileError -{ - const std::vector dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError - if (dirNamesFmt.empty()) //pathological case, but we have to check else this function will wait endlessly - throw zen::FileError(_("A folder input field is empty.")); //should have been checked by caller! - - //detect when volumes are removed/are not available anymore - std::vector>> watches; - - for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it) - { - const Zstring& dirnameFmt = *it; - try - { - //a non-existent network path may block, so check existence asynchronously! - auto ftDirExists = async([=] { return zen::dirExists(dirnameFmt); }); - while (!ftDirExists.timed_wait(boost::posix_time::milliseconds(UI_UPDATE_INTERVAL / 2))) - statusHandler.requestUiRefresh(); //may throw! - if (!ftDirExists.get()) - return WaitResult(dirnameFmt); - - watches.push_back(std::make_pair(dirnameFmt, std::make_shared(dirnameFmt))); //throw FileError, ErrorNotExisting - } - catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! - { - return WaitResult(dirnameFmt); - } - catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) - { - if (!dirExists(dirnameFmt)) //file system race condition!! - return WaitResult(dirnameFmt); - throw; - } - } - - const std::int64_t TICKS_DIR_CHECK_INTERVAL = CHECK_DIR_INTERVAL * ticksPerSec(); //0 on error - TickVal lastCheck = getTicks(); //0 on error - while (true) - { - const bool checkDirExistNow = [&]() -> bool //checking once per sec should suffice - { - const TickVal now = getTicks(); //0 on error - if (dist(lastCheck, now) >= TICKS_DIR_CHECK_INTERVAL) - { - lastCheck = now; - return true; - } - return false; - }(); - - - for (auto it = watches.begin(); it != watches.end(); ++it) - { - const Zstring& dirname = it->first; - DirWatcher& watcher = *(it->second); - - //IMPORTANT CHECK: dirwatcher has problems detecting removal of top watched directories! - if (checkDirExistNow) - if (!dirExists(dirname)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED - return WaitResult(dirname); - - try - { - std::vector changedItems = watcher.getChanges([&] { statusHandler.requestUiRefresh(); }); //throw FileError, ErrorNotExisting - - //remove to be ignored changes - vector_remove_if(changedItems, [](const DirWatcher::Entry& e) - { - return endsWith(e.filename_, Zstr(".ffs_lock")) || //sync.ffs_lock, sync.Del.ffs_lock - endsWith(e.filename_, Zstr(".ffs_db")); //sync.ffs_db, .sync.tmp.ffs_db - //no need to ignore temporal recycle bin directory: this must be caused by a file deletion anyway - }); - - if (!changedItems.empty()) - { - /* - std::for_each(changedItems.begin(), changedItems.end(), - [](const Zstring& fn) { wxMessageBox(toWx(fn));}); - */ - return WaitResult(changedItems[0]); //directory change detected - } - - } - catch (ErrorNotExisting&) //nice atomic behavior: *no* second directory existence check!!! - { - return WaitResult(dirname); - } - catch (FileError&) //play safe: remedy potential FileErrors that should have been ErrorNotExisting (e.g. Linux: errors during directory traversing) - { - if (!dirExists(dirname)) //file system race condition!! - return WaitResult(dirname); - throw; - } - } - - boost::this_thread::sleep(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2)); - statusHandler.requestUiRefresh(true); //throw ?: may start sync at this presumably idle time - } -} - - -//support for monitoring newly connected directories volumes (e.g.: USB-sticks) -void rts::waitForMissingDirs(const std::vector& dirNamesNonFmt, WaitCallback& statusHandler) //throw FileError -{ - while (true) - { - //support specifying volume by name => call getFormattedDirectoryName() repeatedly - const std::vector& dirNamesFmt = getFormattedDirs(dirNamesNonFmt); //throw FileError - - bool allExisting = true; - for (auto it = dirNamesFmt.begin(); it != dirNamesFmt.end(); ++it) - { - const Zstring dirnameFmt = *it; - auto ftDirExisting = async([=]() -> bool - { -#ifdef ZEN_WIN - //1. login to network share, if necessary -> we probably do NOT want multiple concurrent runs: GUI!? - loginNetworkShare(dirnameFmt, false); //login networks shares, no PW prompt -> is this really RTS's job? -#endif - //2. check dir existence - return zen::dirExists(dirnameFmt); - }); - while (!ftDirExisting.timed_wait(boost::posix_time::milliseconds(rts::UI_UPDATE_INTERVAL / 2))) - statusHandler.requestUiRefresh(); //may throw! - - if (!ftDirExisting.get()) - { - allExisting = false; - break; - } - } - if (allExisting) - return; - - //wait some time... - const int refreshInterval = UI_UPDATE_INTERVAL / 2; - assert_static(1000 * CHECK_DIR_INTERVAL % refreshInterval == 0); - for (int i = 0; i < 1000 * CHECK_DIR_INTERVAL / refreshInterval; ++i) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(refreshInterval)); - statusHandler.requestUiRefresh(); - } - } -} diff --git a/RealtimeSync/watcher.h b/RealtimeSync/watcher.h deleted file mode 100644 index 2fd32119..00000000 --- a/RealtimeSync/watcher.h +++ /dev/null @@ -1,54 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef WATCHER_H_INCLUDED -#define WATCHER_H_INCLUDED - -#include -#include - - -namespace rts -{ -const int UI_UPDATE_INTERVAL = 100; //unit: [ms]; perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss -bool updateUiIsAllowed(); - - -class WaitCallback -{ -public: - virtual ~WaitCallback() {} - virtual void requestUiRefresh(bool readyForSync = false) = 0; //opportunity to abort must be implemented in a frequently executed method like requestUiRefresh() -}; - - -//wait until changes are detected or if a directory is not available (anymore) -enum ChangeType -{ - CHANGE_DETECTED, - CHANGE_DIR_MISSING -}; - -struct WaitResult -{ - WaitResult(const zen::DirWatcher::Entry& changedItem) : type(CHANGE_DETECTED), changedItem_(changedItem) {} - WaitResult(const Zstring& dirname) : type(CHANGE_DIR_MISSING), dirname_(dirname) {} - - ChangeType type; - zen::DirWatcher::Entry changedItem_; //for type == CHANGE_DETECTED: file or directory - Zstring dirname_; //for type == CHANGE_DIR_MISSING -}; - -WaitResult waitForChanges(const std::vector& dirNamesNonFmt, - //non-formatted dirnames that yet require call to getFormattedDirectoryName(); empty directories must be checked by caller! - WaitCallback& statusHandler); //throw FileError - -//wait until all directories become available (again) + logs in network share -void waitForMissingDirs(const std::vector& dirNamesNonFmt, - WaitCallback& statusHandler); //throw FileError -} - -#endif // WATCHER_H_INCLUDED diff --git a/RealtimeSync/xml_ffs.cpp b/RealtimeSync/xml_ffs.cpp index 1f3fb1b1..149b0bbe 100644 --- a/RealtimeSync/xml_ffs.cpp +++ b/RealtimeSync/xml_ffs.cpp @@ -7,7 +7,7 @@ #include "xml_ffs.h" #include "../lib/ffs_paths.h" #include -#include +//#include //include FreeFileSync xml headers #include "../lib/process_xml.h" @@ -17,8 +17,6 @@ using namespace zen; xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& batchCfg, const Zstring& filename) { - xmlAccess::XmlRealConfig output; - std::set uniqueFolders; //add main folders @@ -35,13 +33,9 @@ xmlAccess::XmlRealConfig convertBatchToReal(const xmlAccess::XmlBatchConfig& bat uniqueFolders.erase(Zstring()); - output.directories.clear(); - std::transform(uniqueFolders.begin(), uniqueFolders.end(), std::back_inserter(output.directories), - [](const Zstring & fn) { return toWx(fn); }); - - output.commandline = std::wstring(L"\"") + zen::getFreeFileSyncLauncher() + L"\"" + - L" \"" + filename + L"\""; - + xmlAccess::XmlRealConfig output; + output.directories.assign(uniqueFolders.begin(), uniqueFolders.end()); + output.commandline = Zstr("\"") + zen::getFreeFileSyncLauncher() + Zstr("\" \"") + filename + Zstr("\""); return output; } diff --git a/RealtimeSync/xml_proc.h b/RealtimeSync/xml_proc.h index ab57e816..671a237f 100644 --- a/RealtimeSync/xml_proc.h +++ b/RealtimeSync/xml_proc.h @@ -8,7 +8,7 @@ #define XMLPROCESSING_H_INCLUDED #include -#include +//#include #include #include "../lib/xml_base.h" @@ -18,9 +18,9 @@ namespace xmlAccess struct XmlRealConfig { XmlRealConfig() : delay(10) {} - std::vector directories; - wxString commandline; - size_t delay; + std::vector directories; + Zstring commandline; + unsigned int delay; }; void readRealConfig(const Zstring& filename, XmlRealConfig& config); //throw FfsXmlError -- cgit