summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xChangelog.txt26
-rw-r--r--FreeFileSync/Build/Resources/Icons.zipbin388959 -> 393061 bytes
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin522150 -> 522316 bytes
-rwxr-xr-xFreeFileSync/Source/Makefile1
-rwxr-xr-xFreeFileSync/Source/RealTimeSync/Makefile1
-rw-r--r--FreeFileSync/Source/RealTimeSync/application.cpp13
-rw-r--r--FreeFileSync/Source/RealTimeSync/config.cpp13
-rw-r--r--FreeFileSync/Source/RealTimeSync/config.h1
-rw-r--r--FreeFileSync/Source/RealTimeSync/folder_selector2.cpp11
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.cpp12
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.h4
-rw-r--r--FreeFileSync/Source/RealTimeSync/main_dlg.cpp13
-rw-r--r--FreeFileSync/Source/RealTimeSync/monitor.cpp84
-rw-r--r--FreeFileSync/Source/RealTimeSync/tray_menu.cpp11
-rw-r--r--FreeFileSync/Source/afs/abstract.cpp22
-rw-r--r--FreeFileSync/Source/afs/abstract.h2
-rw-r--r--FreeFileSync/Source/afs/abstract_impl.h10
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp95
-rw-r--r--FreeFileSync/Source/afs/ftp.h16
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp222
-rw-r--r--FreeFileSync/Source/afs/gdrive.h16
-rw-r--r--FreeFileSync/Source/afs/init_curl_libssh2.cpp6
-rw-r--r--FreeFileSync/Source/afs/native.cpp18
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp160
-rw-r--r--FreeFileSync/Source/afs/sftp.h21
-rw-r--r--FreeFileSync/Source/application.cpp71
-rw-r--r--FreeFileSync/Source/application.h2
-rw-r--r--FreeFileSync/Source/base/dir_lock.cpp16
-rw-r--r--FreeFileSync/Source/base/resolve_path.cpp84
-rw-r--r--FreeFileSync/Source/base/structures.cpp16
-rw-r--r--FreeFileSync/Source/base/structures.h1
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp6
-rw-r--r--FreeFileSync/Source/base_tools.cpp22
-rw-r--r--FreeFileSync/Source/config.cpp58
-rw-r--r--FreeFileSync/Source/config.h13
-rw-r--r--FreeFileSync/Source/log_file.cpp64
-rw-r--r--FreeFileSync/Source/return_codes.h51
-rw-r--r--FreeFileSync/Source/status_handler.h30
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.cpp196
-rw-r--r--FreeFileSync/Source/ui/batch_status_handler.h23
-rw-r--r--FreeFileSync/Source/ui/cfg_grid.cpp2
-rw-r--r--FreeFileSync/Source/ui/file_grid.cpp40
-rw-r--r--FreeFileSync/Source/ui/folder_selector.cpp17
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp13
-rw-r--r--FreeFileSync/Source/ui/gui_generated.h1
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.cpp195
-rw-r--r--FreeFileSync/Source/ui/gui_status_handler.h12
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp97
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.cpp60
-rw-r--r--FreeFileSync/Source/ui/progress_indicator.h5
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp96
-rw-r--r--FreeFileSync/Source/ui/status_handler_impl.h69
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp2
-rw-r--r--FreeFileSync/Source/ui/tray_icon.cpp2
-rw-r--r--FreeFileSync/Source/ui/tree_grid.cpp2
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--libcurl/curl_wrap.h2
-rw-r--r--libcurl/rest.cpp6
-rw-r--r--xBRZ/src/xbrz.cpp11
-rw-r--r--xBRZ/src/xbrz.h1
-rw-r--r--xBRZ/src/xbrz_config.h1
-rw-r--r--xBRZ/src/xbrz_tools.h1
-rw-r--r--zen/basic_math.h2
-rw-r--r--zen/dir_watcher.cpp24
-rw-r--r--zen/dir_watcher.h25
-rw-r--r--zen/file_access.cpp66
-rw-r--r--zen/file_io.cpp18
-rw-r--r--zen/file_traverser.cpp8
-rw-r--r--zen/guid.h6
-rw-r--r--zen/http.cpp18
-rw-r--r--zen/http.h2
-rw-r--r--zen/open_ssl.cpp193
-rw-r--r--zen/perf.h2
-rw-r--r--zen/process_priority.cpp4
-rw-r--r--zen/recycler.cpp13
-rw-r--r--zen/shell_execute.cpp250
-rw-r--r--zen/shell_execute.h99
-rw-r--r--zen/shutdown.cpp28
-rw-r--r--zen/socket.h20
-rw-r--r--zen/string_base.h2
-rw-r--r--zen/string_tools.h15
-rw-r--r--zen/symlink_target.h6
-rw-r--r--zen/sys_error.cpp23
-rw-r--r--zen/sys_error.h4
-rw-r--r--zen/system.cpp21
-rw-r--r--zen/time.h2
-rw-r--r--zen/zlib_wrap.cpp8
-rw-r--r--zen/zlib_wrap.h2
-rw-r--r--zen/zstring.cpp2
-rw-r--r--zen/zstring.h2
90 files changed, 1619 insertions, 1314 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 8717bc73..31963c7d 100755
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,29 @@
+FreeFileSync 10.23 [2020-04-17]
+-------------------------------
+Run "on completion" commands on console (no need for "cmd.exe /c")
+Check exit code and report errors for external applications
+Report stream output of failed command line calls (macOs, Linux)
+Use Unicode symbols compatible with older macOS
+RealTimeSync: invoke command using cmd.exe instead of ShellExecute (Windows)
+Avoid hitting log file length limitations for aggregated jobs
+Fix OpenSSL failing on HTTP 1.0 response without Content-Length
+Don't allow creating folder names ending with space or dot
+Support base folders with trailing blanks
+Show system error descriptions on volume shadow copy errors
+Raise exit code if saving log file or sending email failed
+Report all documented MTP error descriptions
+Updated default exclude filter (macOS/Linux)
+Added image outlines for improved dark mode support
+Work around WBEM_E_INVALID_CLASS error during installation
+Align file path rendering with app layout direction
+Play sound notification also when "cancel on first error" is set
+Cleaner file path formatting (macOs, Linux)
+Added instructions when failing to start due to missing GTK2 (Ubuntu)
+RealTimeSync: distinguish drive unmount from folder change notification
+Avoid blocking command scripts waiting for user input
+Updated translation files
+
+
FreeFileSync 10.22 [2020-03-18]
-------------------------------
Fixed upper-case conversion bug for non-ASCII strings
diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip
index 0a393bbc..5448052f 100644
--- a/FreeFileSync/Build/Resources/Icons.zip
+++ b/FreeFileSync/Build/Resources/Icons.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/Languages.zip b/FreeFileSync/Build/Resources/Languages.zip
index 3f00d604..39d9b8e7 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Source/Makefile b/FreeFileSync/Source/Makefile
index 5d4f6b20..ce88fe85 100755
--- a/FreeFileSync/Source/Makefile
+++ b/FreeFileSync/Source/Makefile
@@ -90,6 +90,7 @@ CPP_FILES+=../../zen/format_unit.cpp
CPP_FILES+=../../zen/legacy_compiler.cpp
CPP_FILES+=../../zen/open_ssl.cpp
CPP_FILES+=../../zen/process_priority.cpp
+CPP_FILES+=../../zen/shell_execute.cpp
CPP_FILES+=../../zen/shutdown.cpp
CPP_FILES+=../../zen/sys_error.cpp
CPP_FILES+=../../zen/system.cpp
diff --git a/FreeFileSync/Source/RealTimeSync/Makefile b/FreeFileSync/Source/RealTimeSync/Makefile
index 9c7f88e0..ed6e23ac 100755
--- a/FreeFileSync/Source/RealTimeSync/Makefile
+++ b/FreeFileSync/Source/RealTimeSync/Makefile
@@ -31,6 +31,7 @@ CPP_FILES+=../../../zen/file_io.cpp
CPP_FILES+=../../../zen/file_traverser.cpp
CPP_FILES+=../../../zen/format_unit.cpp
CPP_FILES+=../../../zen/legacy_compiler.cpp
+CPP_FILES+=../../../zen/shell_execute.cpp
CPP_FILES+=../../../zen/shutdown.cpp
CPP_FILES+=../../../zen/sys_error.cpp
CPP_FILES+=../../../zen/thread.cpp
diff --git a/FreeFileSync/Source/RealTimeSync/application.cpp b/FreeFileSync/Source/RealTimeSync/application.cpp
index 037d7059..9c1fa138 100644
--- a/FreeFileSync/Source/RealTimeSync/application.cpp
+++ b/FreeFileSync/Source/RealTimeSync/application.cpp
@@ -61,8 +61,8 @@ bool Application::OnInit()
(fff::getResourceDirPf() + "Gtk3Styles.css").c_str(), //const gchar* path,
&error); //GError** error
if (error)
- throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x",
- numberTo<std::wstring>(error->code)),
+ throw SysError(formatSystemError("gtk_css_provider_load_from_data",
+ replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)),
utfTo<std::wstring>(error->message)));
::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen,
@@ -74,9 +74,10 @@ bool Application::OnInit()
#error unknown GTK version!
#endif
+
//Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise:
wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
- wxToolTip::SetAutoPop(10000); //https://msdn.microsoft.com/en-us/library/windows/desktop/aa511495
+ wxToolTip::SetAutoPop(10000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips
SetAppName(L"RealTimeSync");
@@ -161,11 +162,11 @@ int Application::OnRun()
const auto titleFmt = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred");
std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH) << e.what() << '\n';
- return fff::FFS_RC_EXCEPTION;
+ return fff::FFS_EXIT_EXCEPTION;
}
//catch (...) -> let it crash and create mini dump!!!
- return fff::FFS_RC_SUCCESS; //program's return code
+ return fff::FFS_EXIT_SUCCESS; //program's return code
}
@@ -175,5 +176,5 @@ void Application::onQueryEndSession(wxEvent& event)
if (auto mainWin = dynamic_cast<MainDialog*>(GetTopWindow()))
mainWin->onQueryEndSession();
//it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
- terminateProcess(fff::FFS_RC_ABORTED);
+ terminateProcess(fff::FFS_EXIT_ABORTED);
}
diff --git a/FreeFileSync/Source/RealTimeSync/config.cpp b/FreeFileSync/Source/RealTimeSync/config.cpp
index 7051c6d1..2454b941 100644
--- a/FreeFileSync/Source/RealTimeSync/config.cpp
+++ b/FreeFileSync/Source/RealTimeSync/config.cpp
@@ -15,7 +15,7 @@ using namespace zen;
using namespace rts;
//-------------------------------------------------------------------------------------------------------------------------------
-const int XML_FORMAT_RTS_CFG = 1; //2019-05-10
+const int XML_FORMAT_RTS_CFG = 2; //2020-04-14
//-------------------------------------------------------------------------------------------------------------------------------
@@ -68,11 +68,11 @@ void readConfig(const XmlIn& in, XmlRealConfig& cfg, int formatVer)
in["Delay" ](cfg.delay);
in["Commandline"](cfg.commandline);
- //TODO: remove if clause after migration! 2019-05-10
- if (formatVer < 1)
- ;
- else
- in["Commandline"].attribute("HideConsole", cfg.hideConsoleWindow);
+ //TODO: remove if clause after migration! 2020-04-14
+ if (formatVer < 2)
+ if (startsWithAsciiNoCase(cfg.commandline, "cmd /c ") ||
+ startsWithAsciiNoCase(cfg.commandline, "cmd.exe /c "))
+ cfg.commandline = afterFirst(cfg.commandline, Zstr("/c "), IF_MISSING_RETURN_ALL);
}
@@ -81,7 +81,6 @@ void writeConfig(const XmlRealConfig& cfg, XmlOut& out)
out["Directories"](cfg.directories);
out["Delay" ](cfg.delay);
out["Commandline"](cfg.commandline);
- out["Commandline"].attribute("HideConsole", cfg.hideConsoleWindow);
}
}
diff --git a/FreeFileSync/Source/RealTimeSync/config.h b/FreeFileSync/Source/RealTimeSync/config.h
index 75d88aa5..b7b36514 100644
--- a/FreeFileSync/Source/RealTimeSync/config.h
+++ b/FreeFileSync/Source/RealTimeSync/config.h
@@ -17,7 +17,6 @@ struct XmlRealConfig
{
std::vector<Zstring> directories;
Zstring commandline;
- bool hideConsoleWindow = false;
unsigned int delay = 10;
};
diff --git a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
index 7900b5c5..e4b4a451 100644
--- a/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
+++ b/FreeFileSync/Source/RealTimeSync/folder_selector2.cpp
@@ -111,6 +111,9 @@ void FolderSelector2::onFilesDropped(FileDropEvent& event)
}
catch (FileError&) {} //e.g. good for inactive mapped network shares, not so nice for C:\pagefile.sys
+ if (endsWith(itemPath, Zstr(' '))) //prevent getResolvedFilePath() from trimming legit trailing blank!
+ itemPath += FILE_NAME_SEPARATOR;
+
setPath(itemPath);
//event.Skip();
@@ -141,12 +144,16 @@ void FolderSelector2::onSelectDir(wxCommandEvent& event)
}
}
+ Zstring newFolderPath;
wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath)); //put modal wxWidgets dialogs on stack: creating on freestore leads to memleak!
if (dirPicker.ShowModal() != wxID_OK)
return;
- const Zstring newFolderPath = utfTo<Zstring>(dirPicker.GetPath());
+ newFolderPath = utfTo<Zstring>(dirPicker.GetPath());
+
+ if (endsWith(newFolderPath, Zstr(' '))) //prevent getResolvedFilePath() from trimming legit trailing blank!
+ newFolderPath += FILE_NAME_SEPARATOR;
- setFolderPath(newFolderPath, &folderPathCtrl_, folderPathCtrl_, staticText_);
+ setPath(newFolderPath);
}
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
index dccc3495..318c8f1b 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
@@ -251,21 +251,15 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
wxBoxSizer* bSizer13;
bSizer13 = new wxBoxSizer( wxHORIZONTAL );
- m_bitmapCommand = new wxStaticBitmap( m_panelMain, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
- bSizer13->Add( m_bitmapCommand, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
+ m_bitmapConsole = new wxStaticBitmap( m_panelMain, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer13->Add( m_bitmapConsole, 0, wxTOP|wxBOTTOM|wxLEFT|wxALIGN_CENTER_VERTICAL, 5 );
m_staticText6 = new wxStaticText( m_panelMain, wxID_ANY, _("Command line:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText6->Wrap( -1 );
bSizer13->Add( m_staticText6, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
- bSizer13->Add( 0, 0, 1, wxEXPAND, 5 );
-
- m_checkBoxHideConsole = new wxCheckBox( m_panelMain, wxID_ANY, _("&Hide console window"), wxDefaultPosition, wxDefaultSize, 0 );
- bSizer13->Add( m_checkBoxHideConsole, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
-
-
- bSizer141->Add( bSizer13, 0, wxEXPAND, 5 );
+ bSizer141->Add( bSizer13, 0, 0, 5 );
m_textCtrlCommand = new wxTextCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_textCtrlCommand->SetToolTip( _("The command is triggered if:\n- files or subfolders change\n- new folders arrive (e.g. USB stick insert)") );
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h
index 5cc0f0ca..5ecd49d3 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.h
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h
@@ -32,7 +32,6 @@ namespace zen { class BitmapTextButton; }
#include <wx/panel.h>
#include <wx/scrolwin.h>
#include <wx/spinctrl.h>
-#include <wx/checkbox.h>
#include <wx/frame.h>
#include "zen/i18n.h"
@@ -82,9 +81,8 @@ protected:
wxStaticText* m_staticText8;
wxSpinCtrl* m_spinCtrlDelay;
wxStaticLine* m_staticline211;
- wxStaticBitmap* m_bitmapCommand;
+ wxStaticBitmap* m_bitmapConsole;
wxStaticText* m_staticText6;
- wxCheckBox* m_checkBoxHideConsole;
wxTextCtrl* m_textCtrlCommand;
wxStaticLine* m_staticline5;
zen::BitmapTextButton* m_buttonStart;
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 694d6e79..7e6418f4 100644
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -80,13 +80,12 @@ MainDialog::MainDialog(const Zstring& cfgFileName) :
m_txtCtrlDirectoryMain->SetMinSize({fastFromDIP(300), -1});
m_spinCtrlDelay ->SetMinSize({fastFromDIP( 70), -1}); //Hack: set size (why does wxWindow::Size() not work?)
- m_checkBoxHideConsole->Hide(); //only relevant on Windows
m_bpButtonRemoveTopFolder->Hide();
m_panelMainFolder->Layout();
m_bitmapBatch ->SetBitmap(getResourceImage(L"file_batch_sicon"));
m_bitmapFolders->SetBitmap(fff::IconBuffer::genericDirIcon(fff::IconBuffer::SIZE_SMALL));
- m_bitmapCommand->SetBitmap(shrinkImage(getResourceImage(L"command_line").ConvertToImage(), fastFromDIP(20)));
+ m_bitmapConsole->SetBitmap(shrinkImage(getResourceImage(L"command_line").ConvertToImage(), fastFromDIP(20)));
m_bpButtonAddFolder ->SetBitmapLabel(getResourceImage(L"item_add"));
m_bpButtonRemoveTopFolder->SetBitmapLabel(getResourceImage(L"item_remove"));
@@ -358,9 +357,8 @@ void MainDialog::setConfiguration(const XmlRealConfig& cfg)
insertAddFolder(addFolderPaths, 0);
- m_textCtrlCommand ->SetValue(utfTo<wxString>(cfg.commandline));
- m_checkBoxHideConsole->SetValue(cfg.hideConsoleWindow);
- m_spinCtrlDelay ->SetValue(static_cast<int>(cfg.delay));
+ m_textCtrlCommand->SetValue(utfTo<wxString>(cfg.commandline));
+ m_spinCtrlDelay ->SetValue(static_cast<int>(cfg.delay));
}
@@ -373,9 +371,8 @@ XmlRealConfig MainDialog::getConfiguration()
for (const DirectoryPanel* dp : additionalFolderPanels_)
output.directories.push_back(dp->getPath());
- output.commandline = utfTo<Zstring>(m_textCtrlCommand->GetValue());
- output.hideConsoleWindow = m_checkBoxHideConsole->GetValue();
- output.delay = m_spinCtrlDelay->GetValue();
+ output.commandline = utfTo<Zstring>(m_textCtrlCommand->GetValue());
+ output.delay = m_spinCtrlDelay->GetValue();
return output;
}
diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp
index 66a83f3c..dc79609c 100644
--- a/FreeFileSync/Source/RealTimeSync/monitor.cpp
+++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp
@@ -101,25 +101,8 @@ std::set<Zstring, LessNativePath> waitForMissingDirs(const std::vector<Zstring>&
//wait until changes are detected or if a directory is not available (anymore)
-struct WaitResult
-{
- enum ChangeType
- {
- ITEM_CHANGED,
- FOLDER_UNAVAILABLE //1. not existing or 2. can't access
- };
-
- explicit WaitResult(const DirWatcher::Entry& changeEntry) : type(ITEM_CHANGED), changedItem(changeEntry) {}
- explicit WaitResult(const Zstring& folderPath) : type(FOLDER_UNAVAILABLE), missingFolderPath(folderPath) {}
-
- ChangeType type;
- DirWatcher::Entry changedItem; //for type == ITEM_CHANGED: file or directory
- Zstring missingFolderPath; //for type == FOLDER_UNAVAILABLE
-};
-
-
-WaitResult waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths, //throw FileError
- const std::function<void(bool readyForSync)>& requestUiUpdate, std::chrono::milliseconds cbInterval)
+DirWatcher::Change waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths, //throw FileError
+ const std::function<void(bool readyForSync)>& requestUiUpdate, std::chrono::milliseconds cbInterval)
{
assert(std::all_of(folderPaths.begin(), folderPaths.end(), [](const Zstring& folderPath) { return dirAvailable(folderPath); }));
if (folderPaths.empty()) //pathological case, but we have to check else this function will wait endlessly
@@ -135,7 +118,7 @@ WaitResult waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths,
catch (FileError&)
{
if (!dirAvailable(folderPath)) //folder not existing or can't access
- return WaitResult(folderPath);
+ return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath };
throw;
}
@@ -158,12 +141,18 @@ WaitResult waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths,
//IMPORTANT CHECK: DirWatcher has problems detecting removal of top watched directories!
if (checkDirNow)
if (!dirAvailable(folderPath)) //catch errors related to directory removal, e.g. ERROR_NETNAME_DELETED
- return WaitResult(folderPath);
+ return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath };
try
{
- std::vector<DirWatcher::Entry> changedItems = watcher->getChanges([&] { requestUiUpdate(false /*readyForSync*/); /*throw X*/ },
- cbInterval); //throw FileError
- std::erase_if(changedItems, [](const DirWatcher::Entry& e)
+ std::vector<DirWatcher::Change> changes = watcher->fetchChanges([&] { requestUiUpdate(false /*readyForSync*/); /*throw X*/ },
+ cbInterval); //throw FileError
+
+ //give precedence to ChangeType::baseFolderUnavailable
+ for (const DirWatcher::Change& change : changes)
+ if (change.type == DirWatcher::ChangeType::baseFolderUnavailable)
+ return change;
+
+ std::erase_if(changes, [](const DirWatcher::Change& e)
{
return
endsWith(e.itemPath, Zstr(".ffs_tmp")) || //sync.8ea2.ffs_tmp
@@ -172,13 +161,13 @@ WaitResult waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths,
//no need to ignore temporary recycle bin directory: this must be caused by a file deletion anyway
});
- if (!changedItems.empty())
- return WaitResult(changedItems[0]); //directory change detected
+ if (!changes.empty())
+ return changes[0];
}
catch (FileError&)
{
if (!dirAvailable(folderPath)) //a benign(?) race condition with FileError
- return WaitResult(folderPath);
+ return { DirWatcher::ChangeType::baseFolderUnavailable, folderPath };
throw;
}
}
@@ -189,20 +178,21 @@ WaitResult waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths,
}
-inline
-std::wstring getActionName(DirWatcher::ActionType type)
+std::wstring getChangeTypeName(DirWatcher::ChangeType type)
{
switch (type)
{
- case DirWatcher::ACTION_CREATE:
- return L"CREATE";
- case DirWatcher::ACTION_UPDATE:
- return L"UPDATE";
- case DirWatcher::ACTION_DELETE:
- return L"DELETE";
+ case DirWatcher::ChangeType::create:
+ return L"Create";
+ case DirWatcher::ChangeType::update:
+ return L"Update";
+ case DirWatcher::ChangeType::remove:
+ return L"Delete";
+ case DirWatcher::ChangeType::baseFolderUnavailable:
+ return L"Base Folder Unavailable";
}
assert(false);
- return L"ERROR";
+ return L"Error";
}
struct ExecCommandNowException {};
@@ -229,35 +219,29 @@ void rts::monitorDirectories(const std::vector<Zstring>& folderPathPhrases, std:
for (;;) //command executions
{
- DirWatcher::Entry lastChangeDetected;
+ DirWatcher::Change lastChangeDetected;
try
{
for (;;) //detected changes
{
- const WaitResult res = waitForChanges(folderPaths, [&](bool readyForSync) //throw FileError, ExecCommandNowException
+ lastChangeDetected = waitForChanges(folderPaths, [&](bool readyForSync) //throw FileError, ExecCommandNowException
{
requestUiUpdate(nullptr);
if (readyForSync && std::chrono::steady_clock::now() >= nextExecTime)
throw ExecCommandNowException(); //abort wait and start sync
}, cbInterval);
- switch (res.type)
- {
- case WaitResult::ITEM_CHANGED:
- lastChangeDetected = res.changedItem;
- break;
-
- case WaitResult::FOLDER_UNAVAILABLE: //don't execute the command before all directories are available!
- lastChangeDetected = DirWatcher::Entry{ DirWatcher::ACTION_UPDATE, res.missingFolderPath};
- folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiUpdate(&folderPath); }, cbInterval); //throw FileError
- break;
- }
+
+ if (lastChangeDetected.type == DirWatcher::ChangeType::baseFolderUnavailable)
+ //don't execute the command before all directories are available!
+ folderPaths = waitForMissingDirs(folderPathPhrases, [&](const Zstring& folderPath) { requestUiUpdate(&folderPath); }, cbInterval); //throw FileError
+
nextExecTime = std::chrono::steady_clock::now() + delay;
}
}
catch (ExecCommandNowException&) {}
- executeExternalCommand(lastChangeDetected.itemPath, getActionName(lastChangeDetected.action));
+ executeExternalCommand(lastChangeDetected.itemPath, getChangeTypeName(lastChangeDetected.type));
nextExecTime = std::chrono::steady_clock::time_point::max();
}
}
diff --git a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
index 09928566..c2b614ad 100644
--- a/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
+++ b/FreeFileSync/Source/RealTimeSync/tray_menu.cpp
@@ -259,7 +259,7 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri
if (cmdLine.empty())
{
- showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setMainInstructions(_("Incorrect command line:") + L" \"\""));
+ showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setMainInstructions(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine))));
return AbortReason::REQUEST_GUI;
}
@@ -274,12 +274,15 @@ rts::AbortReason rts::runFolderMonitor(const XmlRealConfig& config, const wxStri
auto cmdLineExp = fff::expandMacros(cmdLine);
try
{
- shellExecute(cmdLineExp, ExecutionType::sync, config.hideConsoleWindow); //throw FileError
+ if (const auto [exitCode, output] = consoleExecute(cmdLineExp, std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut)
+ exitCode != 0)
+ throw SysError(formatSystemError("", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
}
- catch (const FileError& e)
+ catch (const SysError& e)
{
//blocks! however, we *expect* this to be a persistent error condition...
- showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString()));
+ showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().
+ setDetailInstructions(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLineExp)) + L"\n\n" + e.toString()));
}
};
diff --git a/FreeFileSync/Source/afs/abstract.cpp b/FreeFileSync/Source/afs/abstract.cpp
index 10cb7995..c775133e 100644
--- a/FreeFileSync/Source/afs/abstract.cpp
+++ b/FreeFileSync/Source/afs/abstract.cpp
@@ -25,6 +25,15 @@ bool fff::isValidRelPath(const Zstring& relPath)
}
+AfsPath fff::sanitizeDeviceRelativePath(Zstring relPath)
+{
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(relPath, Zstr('/'), FILE_NAME_SEPARATOR);
+ if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(relPath, Zstr('\\'), FILE_NAME_SEPARATOR);
+ trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
+ return AfsPath(relPath);
+}
+
+
int AFS::compareDevice(const AbstractFileSystem& lhs, const AbstractFileSystem& rhs)
{
//note: in worst case, order is guaranteed to be stable only during each program run
@@ -224,24 +233,21 @@ AFS::FileCopyResult AFS::copyFileTransactional(const AbstractPath& apSource, con
//perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
moveAndRenameItem(apTargetTmp, apTarget); //throw FileError, (ErrorMoveUnsupported)
- /*
- CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does
+ /* CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does
NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had!
This "feature" is called "File System Tunneling":
https://devblogs.microsoft.com/oldnewthing/?p=34923
- https://support.microsoft.com/kb/172190/en-us
- */
+ https://support.microsoft.com/kb/172190/en-us */
+
return result;
}
else
{
- /*
- Note: non-transactional file copy solves at least four problems:
+ /* Note: non-transactional file copy solves at least four problems:
-> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER
-> network renaming issues
-> allow for true delete before copy to handle low disk space problems
- -> higher performance on unbuffered drives (e.g. USB-sticks)
- */
+ -> higher performance on unbuffered drives (e.g. USB-sticks) */
if (onDeleteTargetFile)
onDeleteTargetFile();
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index d48c5f87..f4f58310 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -16,7 +16,9 @@
namespace fff
{
+struct AfsPath;
bool isValidRelPath(const Zstring& relPath);
+AfsPath sanitizeDeviceRelativePath(Zstring relPath);
struct AbstractFileSystem;
diff --git a/FreeFileSync/Source/afs/abstract_impl.h b/FreeFileSync/Source/afs/abstract_impl.h
index ceabb6b6..d6bec8d2 100644
--- a/FreeFileSync/Source/afs/abstract_impl.h
+++ b/FreeFileSync/Source/afs/abstract_impl.h
@@ -14,16 +14,6 @@
namespace fff
{
-inline
-AfsPath sanitizeRootRelativePath(Zstring relPath)
-{
- if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) replace(relPath, Zstr('/'), FILE_NAME_SEPARATOR);
- if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) replace(relPath, Zstr('\\'), FILE_NAME_SEPARATOR);
- trim(relPath, true, true, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
- return AfsPath(std::move(relPath));
-}
-
-
template <class Function> inline //return ignored error message if available
std::wstring tryReportingDirError(Function cmd /*throw FileError*/, AbstractFileSystem::TraverserCallback& cb /*throw X*/)
{
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index 795692db..33c0884d 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -65,7 +65,7 @@ struct FtpSessionId
bool operator<(const FtpSessionId& lhs, const FtpSessionId& rhs)
{
//exactly the type of case insensitive comparison we need for server names!
- int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs
+ int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
if (rv != 0)
return rv < 0;
@@ -100,13 +100,9 @@ Zstring ansiToUtfEncoding(const std::string& str) //throw SysError
&bytesWritten, //gsize* bytes_written,
&error); //GError** error
if (!utfStr)
- {
- if (!error)
- throw SysError(L"g_convert: unknown error. (" + utfTo<std::wstring>(str) + L')'); //user should never see this
-
- throw SysError(formatSystemError(L"g_convert(" + utfTo<std::wstring>(str) + L')',
- replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), utfTo<std::wstring>(error->message)) );
- }
+ throw SysError(formatSystemError("g_convert(" + utfTo<std::string>(str) + ')',
+ error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"",
+ error ? utfTo<std::wstring>(error->message) : L"Unknown error."));
ZEN_ON_SCOPE_EXIT(::g_free(utfStr));
return { utfStr, bytesWritten };
@@ -130,13 +126,9 @@ std::string utfToAnsiEncoding(const Zstring& str) //throw SysError
&bytesWritten, //gsize* bytes_written,
&error); //GError** error
if (!ansiStr)
- {
- if (!error)
- throw SysError(L"g_convert: unknown error. (" + utfTo<std::wstring>(str) + L')'); //user should never see this
-
- throw SysError(formatSystemError(L"g_convert(" + utfTo<std::wstring>(str) + L')',
- replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)), utfTo<std::wstring>(error->message)));
- }
+ throw SysError(formatSystemError("g_convert(" + utfTo<std::string>(str) + ')',
+ error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"",
+ error ? utfTo<std::wstring>(error->message) : L"Unknown error."));
ZEN_ON_SCOPE_EXIT(::g_free(ansiStr));
return { ansiStr, bytesWritten };
@@ -318,7 +310,7 @@ public:
{
easyHandle_ = ::curl_easy_init();
if (!easyHandle_)
- throw SysError(formatSystemError(L"curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), std::wstring()));
+ throw SysError(formatSystemError("curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), L""));
}
else
::curl_easy_reset(easyHandle_);
@@ -504,7 +496,7 @@ public:
if (nativeErrorCode != 0)
errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
#endif
- throw SysError(formatSystemError(L"curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
+ throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
}
lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
@@ -550,7 +542,7 @@ public:
/*CURLcode rc =*/ ::curl_easy_getinfo(easyHandle_, CURLINFO_FTP_ENTRY_PATH, &homePathCurl);
if (homePathCurl && isAsciiString(homePathCurl))
- return sanitizeRootRelativePath(utfTo<Zstring>(homePathCurl));
+ return sanitizeDeviceRelativePath(utfTo<Zstring>(homePathCurl));
//home path with non-ASCII chars: libcurl issues PWD right after login *before* server was set up for UTF8
//=> CURLINFO_FTP_ENTRY_PATH could be in any encoding => useless!
@@ -579,7 +571,7 @@ public:
const std::string homePathRaw = replaceCpy<std::string>({ itBegin, it }, "\"\"", '"');
const ServerEncoding enc = getServerEncoding(timeoutSec); //throw SysError
const Zstring homePathUtf = serverToUtfEncoding(homePathRaw, enc); //throw SysError
- return sanitizeRootRelativePath(homePathUtf);
+ return sanitizeDeviceRelativePath(homePathUtf);
}
}
}
@@ -627,7 +619,7 @@ private:
{
char* compFmt = ::curl_easy_escape(easyHandle_, comp.c_str(), static_cast<int>(comp.size()));
if (!compFmt)
- throw SysError(replaceCpy<std::wstring>(L"curl_easy_escape: conversion failure (%x)", L"%x", utfTo<std::wstring>(comp)));
+ throw SysError(formatSystemError("curl_easy_escape(" + comp + ')', L"", L"Conversion failure"));
ZEN_ON_SCOPE_EXIT(::curl_free(compFmt));
if (!curlRelPath.empty())
@@ -686,7 +678,7 @@ private:
curl_socket_t currentSocket = 0;
const CURLcode rc = ::curl_easy_getinfo(easyHandle_, CURLINFO_ACTIVESOCKET, &currentSocket);
if (rc != CURLE_OK)
- throw SysError(formatSystemError(L"curl_easy_getinfo(CURLINFO_ACTIVESOCKET)", formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+ throw SysError(formatSystemError("curl_easy_getinfo(CURLINFO_ACTIVESOCKET)", formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
if (currentSocket != CURL_SOCKET_BAD)
return currentSocket;
}
@@ -712,7 +704,7 @@ private:
const auto sf = globalServerFeatures.get();
if (!sf)
- throw SysError(L"FtpSession::getFeatureSupport() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("FtpSession::getFeatureSupport", L"", L"Function call not allowed during init/shutdown."));
sf->access([&](FeatureList& feat) { featureCache_ = feat[sessionId_.server]; });
@@ -902,7 +894,7 @@ void accessFtpSession(const FtpLoginInfo& login, const std::function<void(FtpSes
if (const std::shared_ptr<FtpSessionManager> mgr = globalFtpSessionManager.get())
mgr->access(login, useFtpSession); //throw SysError, X
else
- throw SysError(L"accessFtpSession() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("accessFtpSession", L"", L"Function call not allowed during init/shutdown."));
}
//===========================================================================================================================
@@ -1806,6 +1798,8 @@ class FtpFileSystem : public AbstractFileSystem
public:
FtpFileSystem(const FtpLoginInfo& login) : login_(login) {}
+ const FtpLoginInfo& getLogin() const { return login_; }
+
private:
Zstring getInitPathPhrase(const AfsPath& afsPath) const override { return concatenateFtpFolderPathPhrase(login_, afsPath); }
@@ -1819,7 +1813,7 @@ private:
const FtpLoginInfo& rhs = static_cast<const FtpFileSystem&>(afsRhs).login_;
//exactly the type of case insensitive comparison we need for server names!
- const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs
+ const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
if (rv != 0)
return rv;
@@ -2116,7 +2110,7 @@ private:
//===========================================================================================================================
-//expects "clean" login data, see condenseToFtpFolderPathPhrase()
+//expects "clean" login data
Zstring concatenateFtpFolderPathPhrase(const FtpLoginInfo& login, const AfsPath& afsPath) //noexcept
{
Zstring port;
@@ -2172,11 +2166,10 @@ AfsPath fff::getFtpHomePath(const FtpLoginInfo& login) //throw FileError
}
-Zstring fff::condenseToFtpFolderPathPhrase(const FtpLoginInfo& login, const Zstring& relPath) //noexcept
+AfsDevice fff::condenseToFtpDevice(const FtpLoginInfo& login) //noexcept
{
+ //clean up input:
FtpLoginInfo loginTmp = login;
-
- //clean-up input:
trim(loginTmp.server);
trim(loginTmp.username);
@@ -2188,9 +2181,27 @@ Zstring fff::condenseToFtpFolderPathPhrase(const FtpLoginInfo& login, const Zstr
startsWithAsciiNoCase(loginTmp.server, "ftps:" ) ||
startsWithAsciiNoCase(loginTmp.server, "sftp:" ))
loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE);
- trim(loginTmp.server, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+
+ return makeSharedRef<FtpFileSystem>(loginTmp);
+}
+
+
+FtpLoginInfo fff::extractFtpLogin(const AfsDevice& afsDevice) //noexcept
+{
+ if (const auto ftpDevice = dynamic_cast<const FtpFileSystem*>(&afsDevice.ref()))
+ return ftpDevice->getLogin();
+
+ assert(false);
+ return {};
+}
+
- return concatenateFtpFolderPathPhrase(loginTmp, sanitizeRootRelativePath(relPath));
+bool fff::acceptsItemPathPhraseFtp(const Zstring& itemPathPhrase) //noexcept
+{
+ Zstring path = expandMacros(itemPathPhrase); //expand before trimming!
+ trim(path);
+ return startsWithAsciiNoCase(path, ftpPrefix); //check for explicit FTP path
}
@@ -2198,9 +2209,9 @@ Zstring fff::condenseToFtpFolderPathPhrase(const FtpLoginInfo& login, const Zstr
//
// e.g. ftp://user001:secretpassword@private.example.com:222/mydirectory/
// ftp://user001@private.example.com/mydirectory|pass64=c2VjcmV0cGFzc3dvcmQ
-FtpPathInfo fff::getResolvedFtpPath(const Zstring& folderPathPhrase) //noexcept
+AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
{
- Zstring pathPhrase = expandMacros(folderPathPhrase); //expand before trimming!
+ Zstring pathPhrase = expandMacros(itemPathPhrase); //expand before trimming!
trim(pathPhrase);
if (startsWithAsciiNoCase(pathPhrase, ftpPrefix))
@@ -2212,14 +2223,14 @@ FtpPathInfo fff::getResolvedFtpPath(const Zstring& folderPathPhrase) //noexcept
FtpLoginInfo login;
login.username = decodeFtpUsername(beforeFirst(credentials, Zstr(':'), IF_MISSING_RETURN_ALL)); //support standard FTP syntax, even though ':'
- login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by our concatenateSftpFolderPathPhrase()!
+ login.password = afterFirst(credentials, Zstr(':'), IF_MISSING_RETURN_NONE); //is not used by concatenateFtpFolderPathPhrase()!
const Zstring fullPath = beforeFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_ALL);
const Zstring options = afterFirst(fullPathOpt, Zstr('|'), IF_MISSING_RETURN_NONE);
auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; });
const Zstring serverPort(fullPath.begin(), it);
- const AfsPath serverRelPath = sanitizeRootRelativePath({ it, fullPath.end() });
+ const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() });
login.server = beforeLast(serverPort, Zstr(':'), IF_MISSING_RETURN_ALL);
const Zstring port = afterLast(serverPort, Zstr(':'), IF_MISSING_RETURN_NONE);
@@ -2237,20 +2248,6 @@ FtpPathInfo fff::getResolvedFtpPath(const Zstring& folderPathPhrase) //noexcept
else
assert(false);
} //fix "-Wdangling-else"
- return { login, serverRelPath };
-}
-
-
-bool fff::acceptsItemPathPhraseFtp(const Zstring& itemPathPhrase) //noexcept
-{
- Zstring path = expandMacros(itemPathPhrase); //expand before trimming!
- trim(path);
- return startsWithAsciiNoCase(path, ftpPrefix); //check for explicit FTP path
-}
-
-AbstractPath fff::createItemPathFtp(const Zstring& itemPathPhrase) //noexcept
-{
- const FtpPathInfo& pi = getResolvedFtpPath(itemPathPhrase); //noexcept
- return AbstractPath(makeSharedRef<FtpFileSystem>(pi.login), pi.afsPath);
+ return AbstractPath(makeSharedRef<FtpFileSystem>(login), serverRelPath);
}
diff --git a/FreeFileSync/Source/afs/ftp.h b/FreeFileSync/Source/afs/ftp.h
index 12f8fd1a..2978cdec 100644
--- a/FreeFileSync/Source/afs/ftp.h
+++ b/FreeFileSync/Source/afs/ftp.h
@@ -15,11 +15,11 @@ namespace fff
bool acceptsItemPathPhraseFtp(const Zstring& itemPathPhrase); //noexcept
AbstractPath createItemPathFtp(const Zstring& itemPathPhrase); //noexcept
-//-------------------------------------------------------
-
void ftpInit();
void ftpTeardown();
+//-------------------------------------------------------
+
struct FtpLoginInfo
{
Zstring server;
@@ -31,16 +31,8 @@ struct FtpLoginInfo
//other settings not specific to FTP session:
int timeoutSec = 15;
};
-
-struct FtpPathInfo
-{
- FtpLoginInfo login;
- AfsPath afsPath; //server-relative path
-};
-FtpPathInfo getResolvedFtpPath(const Zstring& folderPathPhrase); //noexcept
-
-//expects (potentially messy) user input:
-Zstring condenseToFtpFolderPathPhrase(const FtpLoginInfo& login, const Zstring& relPath); //noexcept
+AfsDevice condenseToFtpDevice(const FtpLoginInfo& login); //noexcept; potentially messy user input
+FtpLoginInfo extractFtpLogin(const AfsDevice& afsDevice); //noexcept
AfsPath getFtpHomePath(const FtpLoginInfo& login); //throw FileError
}
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index d653a030..d47f780d 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -34,6 +34,11 @@ using AFS = AbstractFileSystem;
namespace fff
{
+struct GdrivePath
+{
+ Zstring userEmail;
+ AfsPath itemPath; //path relative to Google Drive root
+};
bool operator<(const GdrivePath& lhs, const GdrivePath& rhs)
{
const int rv = compareAsciiNoCase(lhs.userEmail, rhs.userEmail);
@@ -86,11 +91,11 @@ struct HttpSessionId
bool operator<(const HttpSessionId& lhs, const HttpSessionId& rhs)
{
//exactly the type of case insensitive comparison we need for server names!
- return compareAsciiNoCase(lhs.server, rhs.server) < 0; //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs
+ return compareAsciiNoCase(lhs.server, rhs.server) < 0; //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
}
-//expects "clean" input data, see condenseToGoogleFolderPathPhrase()
+//expects "clean" input data
Zstring concatenateGoogleFolderPathPhrase(const GdrivePath& gdrivePath) //noexcept
{
Zstring pathPhrase = Zstring(googleDrivePrefix) + FILE_NAME_SEPARATOR + gdrivePath.userEmail;
@@ -284,7 +289,7 @@ HttpSession::Result googleHttpsRequest(const std::string& serverRelPath, //throw
{
const std::shared_ptr<HttpSessionManager> mgr = globalHttpSessionManager.get();
if (!mgr)
- throw SysError(L"googleHttpsRequest() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("googleHttpsRequest", L"", L"Function call not allowed during init/shutdown."));
HttpSession::Result httpResult;
@@ -433,7 +438,7 @@ GoogleAccessInfo authorizeAccessToGoogleDrive(const Zstring& googleLoginHint, co
&hints, //_In_opt_ const ADDRINFOA* pHints,
&servinfo); //_Outptr_ PADDRINFOA* ppResult
if (rcGai != 0)
- throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
if (!servinfo)
throw SysError(L"getaddrinfo: empty server info");
@@ -441,11 +446,11 @@ GoogleAccessInfo authorizeAccessToGoogleDrive(const Zstring& googleLoginHint, co
{
SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol);
if (testSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"socket");
+ THROW_LAST_SYS_ERROR_WSA("socket");
ZEN_ON_SCOPE_FAIL(closeSocket(testSocket));
if (::bind(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"bind");
+ THROW_LAST_SYS_ERROR_WSA("bind");
return testSocket;
};
@@ -470,18 +475,18 @@ GoogleAccessInfo authorizeAccessToGoogleDrive(const Zstring& googleLoginHint, co
sockaddr_storage addr = {}; //"sufficiently large to store address information for IPv4 or IPv6" => sockaddr_in and sockaddr_in6
socklen_t addrLen = sizeof(addr);
if (::getsockname(socket, reinterpret_cast<sockaddr*>(&addr), &addrLen) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"getsockname");
+ THROW_LAST_SYS_ERROR_WSA("getsockname");
if (addr.ss_family != AF_INET &&
addr.ss_family != AF_INET6)
- throw SysError(L"getsockname: unknown protocol family (" + numberTo<std::wstring>(addr.ss_family) + L')');
+ throw SysError(formatSystemError("getsockname", L"", L"Unknown protocol family: " + numberTo<std::wstring>(addr.ss_family)));
const int port = ntohs(reinterpret_cast<const sockaddr_in&>(addr).sin_port);
//the socket is not bound to a specific local IP => inet_ntoa(reinterpret_cast<const sockaddr_in&>(addr).sin_addr) == "0.0.0.0"
const std::string redirectUrl = "http://127.0.0.1:" + numberTo<std::string>(port);
if (::listen(socket, SOMAXCONN) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"listen");
+ THROW_LAST_SYS_ERROR_WSA("listen");
//"A code_verifier is a high-entropy cryptographic random string using the unreserved characters:"
@@ -505,7 +510,7 @@ if (::listen(socket, SOMAXCONN) != 0)
});
try
{
- openWithDefaultApplication(utfTo<Zstring>(oauthUrl)); //throw FileError
+ openWithDefaultApp(utfTo<Zstring>(oauthUrl)); //throw FileError
}
catch (const FileError& e) { throw SysError(e.toString()); } //errors should be further enriched by context info => SysError
@@ -528,7 +533,7 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close
//perf: no significant difference compared to ::WSAPoll()
const int rc = ::select(socket + 1, readfds, nullptr /*writefds*/, nullptr /*errorfds*/, &tv);
if (rc < 0)
- THROW_LAST_SYS_ERROR_WSA(L"select");
+ THROW_LAST_SYS_ERROR_WSA("select");
if (rc != 0)
break;
//else: time-out!
@@ -538,7 +543,7 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close
nullptr, //sockaddr *addr,
nullptr); //int *addrlen
if (clientSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"accept");
+ THROW_LAST_SYS_ERROR_WSA("accept");
//receive first line of HTTP request
std::string reqLine;
@@ -586,7 +591,7 @@ for (;;) //::accept() blocks forever if no client connects (e.g. user just close
try
{
if (!error.empty())
- throw SysError(replaceCpy(_("Error Code %x"), L"%x", + L"\"" + utfTo<std::wstring>(error) + L"\""));
+ throw SysError(replaceCpy(_("Error code %x"), L"%x", + L"\"" + utfTo<std::wstring>(error) + L"\""));
//do as many login-related tasks as possible while we have the browser as an error output device!
//see AFS::connectNetworkFolder() => errors will be lost after time out in dir_exist_async.h!
@@ -655,7 +660,7 @@ void revokeAccessToGoogleDrive(const std::string& accessToken, const Zstring& go
//https://developers.google.com/identity/protocols/OAuth2InstalledApp#tokenrevoke
const std::shared_ptr<HttpSessionManager> mgr = globalHttpSessionManager.get();
if (!mgr)
- throw SysError(L"revokeAccessToGoogleDrive() Function call not allowed during process init/shutdown.");
+ throw SysError(formatSystemError("revokeAccessToGoogleDrive", L"", L"Function call not allowed during init/shutdown."));
HttpSession::Result httpResult;
std::string response;
@@ -730,84 +735,84 @@ std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, const
{
warn_static("perf: trashed=false and ('114231411234' in parents or '123123' in parents)")
- //https://developers.google.com/drive/api/v3/reference/files/list
- std::vector<GoogleFileItem> childItems;
+//https://developers.google.com/drive/api/v3/reference/files/list
+std::vector<GoogleFileItem> childItems;
+{
+ std::optional<std::string> nextPageToken;
+ do
{
- std::optional<std::string> nextPageToken;
- do
+ std::string queryParams = xWwwFormUrlEncode(
{
- std::string queryParams = xWwwFormUrlEncode(
- {
- { "spaces", "drive" }, //
- { "corpora", "user" }, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list
- { "pageSize", "1000" }, //"[1, 1000] Default: 100"
- { "fields", "nextPageToken,incompleteSearch,files(name,id,mimeType,shared,size,modifiedTime,parents)" }, //https://developers.google.com/drive/api/v3/reference/files
- { "q", "trashed=false and '" + folderId + "' in parents" },
- });
- if (nextPageToken)
- queryParams += '&' + xWwwFormUrlEncode({ { "pageToken", *nextPageToken } });
+ { "spaces", "drive" }, //
+ { "corpora", "user" }, //"The 'user' corpus includes all files in "My Drive" and "Shared with me" https://developers.google.com/drive/api/v3/reference/files/list
+ { "pageSize", "1000" }, //"[1, 1000] Default: 100"
+ { "fields", "nextPageToken,incompleteSearch,files(name,id,mimeType,shared,size,modifiedTime,parents)" }, //https://developers.google.com/drive/api/v3/reference/files
+ { "q", "trashed=false and '" + folderId + "' in parents" },
+ });
+ if (nextPageToken)
+ queryParams += '&' + xWwwFormUrlEncode({ { "pageToken", *nextPageToken } });
- std::string response;
- googleHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
+ std::string response;
+ googleHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
- JsonValue jresponse;
- try { jresponse = parseJson(response); }
- catch (JsonParsingError&) {}
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); }
+ catch (JsonParsingError&) {}
- /**/ nextPageToken = getPrimitiveFromJsonObject(jresponse, "nextPageToken");
- const std::optional<std::string> incompleteSearch = getPrimitiveFromJsonObject(jresponse, "incompleteSearch");
- const JsonValue* files = getChildFromJsonObject (jresponse, "files");
- if (!incompleteSearch || *incompleteSearch != "false" || !files || files->type != JsonValue::Type::array)
- throw SysError(formatGoogleErrorRaw(response));
+ /**/ nextPageToken = getPrimitiveFromJsonObject(jresponse, "nextPageToken");
+ const std::optional<std::string> incompleteSearch = getPrimitiveFromJsonObject(jresponse, "incompleteSearch");
+ const JsonValue* files = getChildFromJsonObject (jresponse, "files");
+ if (!incompleteSearch || *incompleteSearch != "false" || !files || files->type != JsonValue::Type::array)
+ throw SysError(formatGoogleErrorRaw(response));
- for (const auto& childVal : files->arrayVal)
- {
- const std::optional<std::string> itemId = getPrimitiveFromJsonObject(childVal, "id");
- const std::optional<std::string> itemName = getPrimitiveFromJsonObject(childVal, "name");
- const std::optional<std::string> mimeType = getPrimitiveFromJsonObject(childVal, "mimeType");
- const std::optional<std::string> shared = getPrimitiveFromJsonObject(childVal, "shared");
- const std::optional<std::string> size = getPrimitiveFromJsonObject(childVal, "size");
- const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(childVal, "modifiedTime");
- const JsonValue* parents = getChildFromJsonObject (childVal, "parents");
-
- if (!itemId || !itemName || !mimeType || !modifiedTime || !parents)
- throw SysError(formatGoogleErrorRaw(response));
+ for (const auto& childVal : files->arrayVal)
+ {
+ const std::optional<std::string> itemId = getPrimitiveFromJsonObject(childVal, "id");
+ const std::optional<std::string> itemName = getPrimitiveFromJsonObject(childVal, "name");
+ const std::optional<std::string> mimeType = getPrimitiveFromJsonObject(childVal, "mimeType");
+ const std::optional<std::string> shared = getPrimitiveFromJsonObject(childVal, "shared");
+ const std::optional<std::string> size = getPrimitiveFromJsonObject(childVal, "size");
+ const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(childVal, "modifiedTime");
+ const JsonValue* parents = getChildFromJsonObject (childVal, "parents");
- const bool isFolder = *mimeType == googleFolderMimeType;
- const bool isShared = shared && *shared == "true"; //"Not populated for items in shared drives"
- const uint64_t fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
+ if (!itemId || !itemName || !mimeType || !modifiedTime || !parents)
+ throw SysError(formatGoogleErrorRaw(response));
- //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL));
- if (tc == TimeComp() || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
- throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')');
+ const bool isFolder = *mimeType == googleFolderMimeType;
+ const bool isShared = shared && *shared == "true"; //"Not populated for items in shared drives"
+ const uint64_t fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
- time_t modTime = utcToTimeT(tc); //returns -1 on error
- if (modTime == -1)
- {
- if (tc.year == 1600 || //zero-initialized FILETIME equals "December 31, 1600" or "January 1, 1601"
- tc.year == 1601) // => yes, possible even on Google Drive: https://freefilesync.org/forum/viewtopic.php?t=6602
- modTime = 0;
- else
- throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')');
- }
+ //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
+ const TimeComp tc = parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL));
+ if (tc == TimeComp() || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')');
- std::vector<std::string> parentIds;
- for (const auto& parentVal : parents->arrayVal)
- {
- if (parentVal.type != JsonValue::Type::string)
- throw SysError(formatGoogleErrorRaw(response));
- parentIds.push_back(parentVal.primVal);
- }
- assert(std::find(parentIds.begin(), parentIds.end(), folderId) != parentIds.end());
+ time_t modTime = utcToTimeT(tc); //returns -1 on error
+ if (modTime == -1)
+ {
+ if (tc.year == 1600 || //zero-initialized FILETIME equals "December 31, 1600" or "January 1, 1601"
+ tc.year == 1601) // => yes, possible even on Google Drive: https://freefilesync.org/forum/viewtopic.php?t=6602
+ modTime = 0;
+ else
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L')');
+ }
- childItems.push_back({ *itemId, { *itemName, isFolder, isShared, fileSize, modTime, std::move(parentIds) } });
+ std::vector<std::string> parentIds;
+ for (const auto& parentVal : parents->arrayVal)
+ {
+ if (parentVal.type != JsonValue::Type::string)
+ throw SysError(formatGoogleErrorRaw(response));
+ parentIds.push_back(parentVal.primVal);
}
+ assert(std::find(parentIds.begin(), parentIds.end(), folderId) != parentIds.end());
+
+ childItems.push_back({ *itemId, { *itemName, isFolder, isShared, fileSize, modTime, std::move(parentIds) } });
}
- while (nextPageToken);
}
- return childItems;
+ while (nextPageToken);
+}
+return childItems;
}
@@ -1085,7 +1090,7 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren
if (!std::any_of(parents->arrayVal.begin(), parents->arrayVal.end(),
[&](const JsonValue& jval) { return jval.type == JsonValue::Type::string && jval.primVal == parentIdTo; }))
- throw SysError(L"gdriveMoveAndRenameItem: Google Drive internal failure"); //user should never see this...
+ throw SysError(formatSystemError("gdriveMoveAndRenameItem", L"", L"Google Drive internal failure.")); //user should never see this...
}
@@ -2069,7 +2074,7 @@ GooglePersistentSessions::AsyncAccessInfo accessGlobalFileState(const Zstring& g
if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
return gps->accessGlobalFileState(googleUserEmail, useFileState); //throw SysError, X
- throw SysError(L"accessGlobalFileState() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("accessGlobalFileState", L"", L"Function call not allowed during init/shutdown."));
}
//==========================================================================================
@@ -2399,10 +2404,18 @@ class GdriveFileSystem : public AbstractFileSystem
public:
GdriveFileSystem(const Zstring& googleUserEmail) : googleUserEmail_(googleUserEmail) {}
+ const Zstring& getEmail() const { return googleUserEmail_; }
+
private:
GdrivePath getGdrivePath(const AfsPath& afsPath) const { return { googleUserEmail_, afsPath }; }
- Zstring getInitPathPhrase(const AfsPath& afsPath) const override { return concatenateGoogleFolderPathPhrase(getGdrivePath(afsPath)); }
+ Zstring getInitPathPhrase(const AfsPath& afsPath) const override
+ {
+ Zstring initPathPhrase = concatenateGoogleFolderPathPhrase(getGdrivePath(afsPath));
+ if (endsWith(initPathPhrase, Zstr(' '))) //path prase concept must survive trimming!
+ initPathPhrase += FILE_NAME_SEPARATOR;
+ return initPathPhrase;
+ }
std::wstring getDisplayPath(const AfsPath& afsPath) const override { return getGoogleDisplayPath(getGdrivePath(afsPath)); }
@@ -2707,7 +2720,7 @@ private:
{
const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get();
if (!gps)
- throw SysError(L"GdriveFileSystem::authenticateAccess() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("GdriveFileSystem::authenticateAccess", L"", L"Function call not allowed during init/shutdown."));
for (const Zstring& email : gps->listUserSessions()) //throw SysError
if (equalAsciiNoCase(email, googleUserEmail_))
@@ -2800,7 +2813,7 @@ Zstring fff::googleAddUser(const std::function<void()>& updateGui /*throw X*/) /
if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
return gps->addUserSession(Zstr("") /*googleLoginHint*/, updateGui); //throw SysError, X
- throw SysError(L"googleAddUser() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("googleAddUser", L"", L"Function call not allowed during init/shutdown."));
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive"), e.toString()); }
}
@@ -2813,7 +2826,7 @@ void fff::googleRemoveUser(const Zstring& googleUserEmail) //throw FileError
if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
return gps->removeUserSession(googleUserEmail); //throw SysError
- throw SysError(L"googleRemoveUser() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("googleRemoveUser", L"", L"Function call not allowed during init/shutdown."));
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to disconnect from %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), e.toString()); }
}
@@ -2826,34 +2839,25 @@ std::vector<Zstring> /*Google user email*/ fff::googleListConnectedUsers() //thr
if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
return gps->listUserSessions(); //throw SysError
- throw SysError(L"googleListConnectedUsers() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("googleListConnectedUsers", L"", L"Function call not allowed during init/shutdown."));
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", L"Google Drive"), e.toString()); }
}
-Zstring fff::condenseToGoogleFolderPathPhrase(const Zstring& userEmail, const Zstring& relPath) //noexcept
+AfsDevice fff::condenseToGdriveDevice(const Zstring& userEmail) //noexcept
{
- return concatenateGoogleFolderPathPhrase({ trimCpy(userEmail), sanitizeRootRelativePath(relPath) });
+ return makeSharedRef<GdriveFileSystem>(trimCpy(userEmail));
}
-//e.g.: gdrive:/john@gmail.com/folder/file.txt
-GdrivePath fff::getResolvedGooglePath(const Zstring& folderPathPhrase) //noexcept
+Zstring fff::extractGdriveEmail(const AfsDevice& afsDevice) //noexcept
{
- Zstring path = folderPathPhrase;
- path = expandMacros(path); //expand before trimming!
- trim(path);
+ if (const auto gdriveDevice = dynamic_cast<const GdriveFileSystem*>(&afsDevice.ref()))
+ return gdriveDevice ->getEmail();
- if (startsWithAsciiNoCase(path, googleDrivePrefix))
- path = path.c_str() + strLength(googleDrivePrefix);
-
- const AfsPath& sanPath = sanitizeRootRelativePath(path); //Win/macOS compatibility: let's ignore slash/backslash differences
-
- const Zstring& userEmail = beforeFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const AfsPath afsPath (afterFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
-
- return { userEmail, afsPath };
+ assert(false);
+ return {};
}
@@ -2865,8 +2869,20 @@ bool fff::acceptsItemPathPhraseGdrive(const Zstring& itemPathPhrase) //noexcept
}
+//e.g.: gdrive:/john@gmail.com/folder/file.txt
AbstractPath fff::createItemPathGdrive(const Zstring& itemPathPhrase) //noexcept
{
- const GdrivePath& gdrivePath = getResolvedGooglePath(itemPathPhrase); //noexcept
- return AbstractPath(makeSharedRef<GdriveFileSystem>(gdrivePath.userEmail), gdrivePath.itemPath);
+ Zstring path = itemPathPhrase;
+ path = expandMacros(path); //expand before trimming!
+ trim(path);
+
+ if (startsWithAsciiNoCase(path, googleDrivePrefix))
+ path = path.c_str() + strLength(googleDrivePrefix);
+
+ const AfsPath& sanPath = sanitizeDeviceRelativePath(path); //Win/macOS compatibility: let's ignore slash/backslash differences
+
+ const Zstring& userEmail = beforeFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ const AfsPath afsPath (afterFirst(sanPath.value, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE));
+
+ return AbstractPath(makeSharedRef<GdriveFileSystem>(userEmail), afsPath);
}
diff --git a/FreeFileSync/Source/afs/gdrive.h b/FreeFileSync/Source/afs/gdrive.h
index d81620a3..fb90a2ab 100644
--- a/FreeFileSync/Source/afs/gdrive.h
+++ b/FreeFileSync/Source/afs/gdrive.h
@@ -14,26 +14,18 @@ namespace fff
bool acceptsItemPathPhraseGdrive(const Zstring& itemPathPhrase); //noexcept
AbstractPath createItemPathGdrive(const Zstring& itemPathPhrase); //noexcept
-//-------------------------------------------------------
-
void googleDriveInit(const Zstring& configDirPath, //directory to store Google-Drive-specific files
const Zstring& caCertFilePath); //cacert.pem
void googleDriveTeardown();
+//-------------------------------------------------------
+
Zstring /*Google user email*/ googleAddUser(const std::function<void()>& updateGui /*throw X*/); //throw FileError, X
void googleRemoveUser(const Zstring& googleUserEmail); //throw FileError
std::vector<Zstring> /*Google user email*/ googleListConnectedUsers(); //throw FileError
-
-struct GdrivePath
-{
- Zstring userEmail;
- AfsPath itemPath; //path relative to Google Drive root => no leading or trailing backslash!
-};
-GdrivePath getResolvedGooglePath(const Zstring& folderPathPhrase); //noexcept
-
-//expects (potentially messy) user input:
-Zstring condenseToGoogleFolderPathPhrase(const Zstring& userEmail, const Zstring& relPath); //noexcept
+AfsDevice condenseToGdriveDevice(const Zstring& userEmail); //noexcept; potentially messy user input
+Zstring extractGdriveEmail(const AfsDevice& afsDevice); //noexcept
}
#endif //FS_GDRIVE_9238425018342701356
diff --git a/FreeFileSync/Source/afs/init_curl_libssh2.cpp b/FreeFileSync/Source/afs/init_curl_libssh2.cpp
index 57cbfa95..d1645ee1 100644
--- a/FreeFileSync/Source/afs/init_curl_libssh2.cpp
+++ b/FreeFileSync/Source/afs/init_curl_libssh2.cpp
@@ -73,7 +73,7 @@ public:
assert(sessionCount_ >= 0);
if (!newSessionsAllowed_)
- throw SysError(L"UniSessionCounter::inc() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("UniSessionCounter::inc", L"", L"Function call not allowed during init/shutdown."));
++sessionCount_;
}
@@ -148,8 +148,8 @@ std::shared_ptr<UniCounterCookie> zen::getLibsshCurlUnifiedInitCookie(Global<Uni
{
std::shared_ptr<UniSessionCounter> sessionCounter = globalSftpSessionCount.get();
if (!sessionCounter)
- throw SysError(L"getLibsshCurlUnifiedInitCookie() function call not allowed during init/shutdown."); //=> ~UniCounterCookie() *not* called!
- sessionCounter->pimpl->inc(); //throw SysError //
+ throw SysError(formatSystemError("getLibsshCurlUnifiedInitCookie", L"", L"Function call not allowed during init/shutdown.")); //=> ~UniCounterCookie() *not* called!
+ sessionCounter->pimpl->inc(); //throw SysError //
//pass "ownership" of having to call UniSessionCounter::dec()
return std::make_shared<UniCounterCookie>(sessionCounter); //throw SysError
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index 6e1c96fc..78ced1be 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -60,7 +60,7 @@ NativeFileInfo getFileAttributes(FileBase::FileHandle fh) //throw SysError
{
struct ::stat fileAttr = {};
if (::fstat(fh, &fileAttr) != 0)
- THROW_LAST_SYS_ERROR(L"fstat");
+ THROW_LAST_SYS_ERROR("fstat");
return
{
@@ -84,7 +84,7 @@ std::vector<FsItemRaw> getDirContentFlat(const Zstring& dirPath) //throw FileErr
DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
if (!folder)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), "opendir");
ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash
std::vector<FsItemRaw> output;
@@ -107,7 +107,7 @@ std::vector<FsItemRaw> getDirContentFlat(const Zstring& dirPath) //throw FileErr
if (errno == 0) //errno left unchanged => no more items
return output;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), "readdir");
//don't retry but restart dir traversal on error! https://devblogs.microsoft.com/oldnewthing/20140612-00/?p=753
}
@@ -163,7 +163,7 @@ ItemDetailsRaw getItemDetails(const Zstring& itemPath) //throw FileError
{
struct ::stat statData = {};
if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat");
return { S_ISLNK(statData.st_mode) ? ItemType::SYMLINK : //on Linux there is no distinction between file and directory symlinks!
(S_ISDIR(statData.st_mode) ? ItemType::FOLDER :
@@ -175,7 +175,7 @@ ItemDetailsRaw getSymlinkTargetDetails(const Zstring& linkPath) //throw FileErro
{
struct ::stat statData = {};
if (::stat(linkPath.c_str(), &statData) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), "stat");
return { S_ISDIR(statData.st_mode) ? ItemType::FOLDER : ItemType::FILE, statData.st_mtime, makeUnsigned(statData.st_size), generateFileId(statData) };
}
@@ -371,7 +371,13 @@ private:
std::optional<Zstring> getNativeItemPath(const AfsPath& afsPath) const override { return getNativePath(afsPath); }
- Zstring getInitPathPhrase(const AfsPath& afsPath) const override { return getNativePath(afsPath); }
+ Zstring getInitPathPhrase(const AfsPath& afsPath) const override
+ {
+ Zstring initPathPhrase = getNativePath(afsPath);
+ if (endsWith(initPathPhrase, Zstr(' '))) //path prase concept must survive trimming!
+ initPathPhrase += FILE_NAME_SEPARATOR;
+ return initPathPhrase;
+ }
std::wstring getDisplayPath(const AfsPath& afsPath) const override { return utfTo<std::wstring>(getNativePath(afsPath)); }
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index ed0ff13e..c19b8974 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -123,7 +123,7 @@ struct SshSessionId
bool operator<(const SshSessionId& lhs, const SshSessionId& rhs)
{
//exactly the type of case insensitive comparison we need for server names!
- int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs
+ int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
if (rv != 0)
return rv < 0;
@@ -207,11 +207,11 @@ public:
sshSession_ = ::libssh2_session_init();
if (!sshSession_) //does not set ssh last error; source: only memory allocation may fail
- throw SysError(formatSystemError(L"libssh2_session_init", formatSshStatusCode(LIBSSH2_ERROR_ALLOC), std::wstring()));
+ throw SysError(formatSystemError("libssh2_session_init", formatSshStatusCode(LIBSSH2_ERROR_ALLOC), std::wstring()));
//if zlib compression causes trouble, make it a user setting: https://freefilesync.org/forum/viewtopic.php?t=6663
if (const int rc = ::libssh2_session_flag(sshSession_, LIBSSH2_FLAG_COMPRESS, 1); rc != 0) //does not set ssh last error
- throw SysError(formatSystemError(L"libssh2_session_flag", formatSshStatusCode(rc), std::wstring()));
+ throw SysError(formatSystemError("libssh2_session_flag", formatSshStatusCode(rc), std::wstring()));
::libssh2_session_set_blocking(sshSession_, 1);
@@ -220,7 +220,7 @@ public:
if (::libssh2_session_handshake(sshSession_, socket_->get()) != 0)
- throw SysError(formatLastSshError(L"libssh2_session_handshake", nullptr));
+ throw SysError(formatLastSshError("libssh2_session_handshake", nullptr));
//evaluate fingerprint = libssh2_hostkey_hash(sshSession_, LIBSSH2_HOSTKEY_HASH_SHA1) ???
@@ -231,7 +231,7 @@ public:
if (!authList)
{
if (::libssh2_userauth_authenticated(sshSession_) == 0)
- throw SysError(formatLastSshError(L"libssh2_userauth_list", nullptr));
+ throw SysError(formatLastSshError("libssh2_userauth_list", nullptr));
//else: SSH_USERAUTH_NONE has authenticated successfully => we're already done
}
else
@@ -257,7 +257,7 @@ public:
if (supportAuthPassword)
{
if (::libssh2_userauth_password(sshSession_, usernameUtf8, passwordUtf8) != 0)
- throw SysError(formatLastSshError(L"libssh2_userauth_password", nullptr));
+ throw SysError(formatLastSshError("libssh2_userauth_password", nullptr));
}
else if (supportAuthInteractive) //some servers, e.g. web.sourceforge.net, support "keyboard-interactive", but not "password"
{
@@ -297,7 +297,7 @@ public:
ZEN_ON_SCOPE_EXIT(*::libssh2_session_abstract(sshSession_) = nullptr);
if (::libssh2_userauth_keyboard_interactive(sshSession_, usernameUtf8, authCallbackWrapper) != 0)
- throw SysError(formatLastSshError(L"libssh2_userauth_keyboard_interactive", nullptr) +
+ throw SysError(formatLastSshError("libssh2_userauth_keyboard_interactive", nullptr) +
(unexpectedPrompts.empty() ? L"" : L"\nUnexpected prompts: " + unexpectedPrompts));
}
else
@@ -369,7 +369,7 @@ public:
replaceCpy<std::wstring>(L"%x is not an OpenSSH or PuTTY private key file.", L"%x",
fmtPath(sessionId_.privateKeyFilePath) + L" [" + invalidKeyFormat + L']'));
- throw SysError(formatLastSshError(L"libssh2_userauth_publickey_frommemory", nullptr));
+ throw SysError(formatLastSshError("libssh2_userauth_publickey_frommemory", nullptr));
}
}
break;
@@ -378,15 +378,15 @@ public:
{
LIBSSH2_AGENT* sshAgent = ::libssh2_agent_init(sshSession_);
if (!sshAgent)
- throw SysError(formatLastSshError(L"libssh2_agent_init", nullptr));
+ throw SysError(formatLastSshError("libssh2_agent_init", nullptr));
ZEN_ON_SCOPE_EXIT(::libssh2_agent_free(sshAgent));
if (::libssh2_agent_connect(sshAgent) != 0)
- throw SysError(formatLastSshError(L"libssh2_agent_connect", nullptr));
+ throw SysError(formatLastSshError("libssh2_agent_connect", nullptr));
ZEN_ON_SCOPE_EXIT(::libssh2_agent_disconnect(sshAgent));
if (::libssh2_agent_list_identities(sshAgent) != 0)
- throw SysError(formatLastSshError(L"libssh2_agent_list_identities", nullptr));
+ throw SysError(formatLastSshError("libssh2_agent_list_identities", nullptr));
for (libssh2_agent_publickey* prev = nullptr;;)
{
@@ -397,7 +397,7 @@ public:
else if (rc == 1) //no more public keys
throw SysError(L"SSH agent contains no matching public key.");
else
- throw SysError(formatLastSshError(L"libssh2_agent_get_identity", nullptr));
+ throw SysError(formatLastSshError("libssh2_agent_get_identity", nullptr));
if (::libssh2_agent_userauth(sshAgent, usernameUtf8.c_str(), identity) == 0)
break; //authentication successful
@@ -446,7 +446,7 @@ public:
size_t getSftpChannelCount() const { return sftpChannels_.size(); }
//return "false" if pending
- bool tryNonBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const std::wstring& functionName,
+ bool tryNonBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const char* functionName,
const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/, int timeoutSec) //throw SysError, FatalSshError
{
assert(::libssh2_session_get_blocking(sshSession_));
@@ -514,10 +514,8 @@ public:
throw FatalSshError(_P("Cannot wait on more than 1 connection at a time.", "Cannot wait on more than %x connections at a time.", FD_SETSIZE) + L' ' +
replaceCpy(_("Active connections: %x"), L"%x", numberTo<std::wstring>(sshSessions.size())));
SocketType nfds = 0;
- fd_set rfd = {};
+ fd_set rfd = {}; //includes FD_ZERO
fd_set wfd = {};
- FD_ZERO(&wfd);
- FD_ZERO(&rfd);
fd_set* writefds = nullptr;
fd_set* readfds = nullptr;
@@ -568,15 +566,16 @@ public:
//WSAPoll broken, even ::poll() on OS X? https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
//perf: no significant difference compared to ::WSAPoll()
- const int rc = ::select(nfds + 1, readfds, writefds, nullptr /*errorfds*/, &tv);
+ const int rc = ::select(nfds + 1, //int nfds,
+ readfds, //fd_set* readfds,
+ writefds, //fd_set* writefds,
+ nullptr, //fd_set* exceptfds,
+ &tv); //struct timeval* timeout
if (rc == 0)
return; //time-out! => let next tryNonBlocking() call fail with detailed error!
if (rc < 0)
- {
//consider SSH sessions corrupted! => isHealthy() will see pending commands
- ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
- throw FatalSshError(formatSystemError(L"select", ec));
- }
+ throw FatalSshError(formatSystemError("select", getLastError()));
}
static void addSftpChannel(const std::vector<SshSession*>& sshSessions, int timeoutSec) //throw SysError, FatalSshError
@@ -601,7 +600,7 @@ public:
for (size_t pos = pendingSessions.size(); pos-- > 0 ; ) //CAREFUL WITH THESE ERASEs (invalidate positions!!!)
try
{
- if (pendingSessions[pos]->tryNonBlocking(static_cast<size_t>(-1), sftpCommandStartTime, L"libssh2_sftp_init",
+ if (pendingSessions[pos]->tryNonBlocking(static_cast<size_t>(-1), sftpCommandStartTime, "libssh2_sftp_init",
[&](const SshSession::Details& sd) //noexcept!
{
LIBSSH2_SFTP* sftpChannelNew = ::libssh2_sftp_init(sd.sshSession);
@@ -661,7 +660,7 @@ private:
}
}
- std::wstring formatLastSshError(const std::wstring& functionName, LIBSSH2_SFTP* sftpChannel /*optional*/) const
+ std::wstring formatLastSshError(const char* functionName, LIBSSH2_SFTP* sftpChannel /*optional*/) const
{
char* lastErrorMsg = nullptr; //owned by "sshSession"
const int sshStatusCode = ::libssh2_session_last_error(sshSession_, &lastErrorMsg, nullptr, false /*want_buf*/);
@@ -681,7 +680,7 @@ private:
{
bool commandPending = false;
std::chrono::steady_clock::time_point commandStartTime; //specified by client, try to detect libssh2 usage errors
- std::wstring functionName;
+ std::string functionName;
};
struct SftpChannelInfo
@@ -744,7 +743,7 @@ public:
//bool isHealthy() const { return session_->isHealthy(); }
- void executeBlocking(const std::wstring& functionName, const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/) //throw SysError, FatalSshError
+ void executeBlocking(const char* functionName, const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/) //throw SysError, FatalSshError
{
assert(threadId_ == getThreadId());
assert(session_->getSftpChannelCount() > 0);
@@ -769,13 +768,13 @@ public:
SshSessionExclusive(std::unique_ptr<SshSession, ReUseOnDelete>&& idleSession, int timeoutSec) :
session_(std::move(idleSession)) /*bound!*/, timeoutSec_(timeoutSec) { /*assert(session_->isHealthy());*/ }
- bool tryNonBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const std::wstring& functionName, //throw SysError, FatalSshError
+ bool tryNonBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const char* functionName, //throw SysError, FatalSshError
const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/)
{
return session_->tryNonBlocking(channelNo, commandStartTime, functionName, sftpCommand, timeoutSec_); //throw SysError, FatalSshError
}
- void finishBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const std::wstring& functionName,
+ void finishBlocking(size_t channelNo, std::chrono::steady_clock::time_point commandStartTime, const char* functionName,
const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/)
{
for (;;)
@@ -998,7 +997,7 @@ std::shared_ptr<SftpSessionManager::SshSessionShared> getSharedSftpSession(const
if (const std::shared_ptr<SftpSessionManager> mgr = globalSftpSessionManager.get())
return mgr->getSharedSession(login); //throw SysError
- throw SysError(L"getSharedSftpSession() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("getSharedSftpSession", L"", L"Function call not allowed during init/shutdown."));
}
@@ -1007,11 +1006,11 @@ std::unique_ptr<SftpSessionManager::SshSessionExclusive> getExclusiveSftpSession
if (const std::shared_ptr<SftpSessionManager> mgr = globalSftpSessionManager.get())
return mgr->getExclusiveSession(login); //throw SysError
- throw SysError(L"getExclusiveSftpSession() function call not allowed during init/shutdown.");
+ throw SysError(formatSystemError("getExclusiveSftpSession", L"", L"Function call not allowed during init/shutdown."));
}
-void runSftpCommand(const SftpLoginInfo& login, const std::wstring& functionName,
+void runSftpCommand(const SftpLoginInfo& login, const char* functionName,
const std::function<int(const SshSession::Details& sd)>& sftpCommand /*noexcept!*/) //throw SysError
{
std::shared_ptr<SftpSessionManager::SshSessionShared> asyncSession = getSharedSftpSession(login); //throw SysError
@@ -1041,7 +1040,7 @@ std::vector<SftpItem> getDirContentFlat(const SftpLoginInfo& login, const AfsPat
LIBSSH2_SFTP_HANDLE* dirHandle = nullptr;
try
{
- runSftpCommand(login, L"libssh2_sftp_opendir", //throw SysError
+ runSftpCommand(login, "libssh2_sftp_opendir", //throw SysError
[&](const SshSession::Details& sd) //noexcept!
{
dirHandle = ::libssh2_sftp_opendir(sd.sftpChannel, getLibssh2Path(dirPath));
@@ -1054,7 +1053,7 @@ std::vector<SftpItem> getDirContentFlat(const SftpLoginInfo& login, const AfsPat
ZEN_ON_SCOPE_EXIT(try
{
- runSftpCommand(login, L"libssh2_sftp_closedir", //throw SysError
+ runSftpCommand(login, "libssh2_sftp_closedir", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_closedir(dirHandle); }); //noexcept!
}
catch (SysError&) {});
@@ -1067,7 +1066,7 @@ std::vector<SftpItem> getDirContentFlat(const SftpLoginInfo& login, const AfsPat
int rc = 0;
try
{
- runSftpCommand(login, L"libssh2_sftp_readdir", //throw SysError
+ runSftpCommand(login, "libssh2_sftp_readdir", //throw SysError
[&](const SshSession::Details& sd) { return rc = ::libssh2_sftp_readdir(dirHandle, &buffer[0], buffer.size(), &attribs); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, dirPath))), e.toString()); }
@@ -1111,7 +1110,7 @@ SftpItemDetails getSymlinkTargetDetails(const SftpLoginInfo& login, const AfsPat
LIBSSH2_SFTP_ATTRIBUTES attribsTrg = {};
try
{
- runSftpCommand(login, L"libssh2_sftp_stat", //throw SysError
+ runSftpCommand(login, "libssh2_sftp_stat", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_stat(sd.sftpChannel, getLibssh2Path(linkPath), &attribsTrg); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getSftpDisplayPath(login.server, linkPath))), e.toString()); }
@@ -1227,7 +1226,7 @@ struct InputStreamSftp : public AbstractFileSystem::InputStream
{
session_ = getSharedSftpSession(login); //throw SysError
- session_->executeBlocking(L"libssh2_sftp_open", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_open", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) //noexcept!
{
fileHandle_ = ::libssh2_sftp_open(sd.sftpChannel, getLibssh2Path(filePath), LIBSSH2_FXF_READ, 0);
@@ -1244,7 +1243,7 @@ struct InputStreamSftp : public AbstractFileSystem::InputStream
{
try
{
- session_->executeBlocking(L"libssh2_sftp_close", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_close", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_close(fileHandle_); }); //noexcept!
}
catch (const SysError&) {}
@@ -1300,7 +1299,7 @@ private:
ssize_t bytesRead = 0;
try
{
- session_->executeBlocking(L"libssh2_sftp_read", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_read", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) //noexcept!
{
bytesRead = ::libssh2_sftp_read(fileHandle_, static_cast<char*>(buffer), bytesToRead);
@@ -1308,7 +1307,7 @@ private:
});
if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
- throw SysError(L"libssh2_sftp_read: buffer overflow."); //user should never see this
+ throw SysError(formatSystemError("libssh2_sftp_read", L"", L"Buffer overflow.")); //user should never see this
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
catch (const FatalSshError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(displayPath_)), e.toString()); } //SSH session corrupted! => caller (will/should) stop using session
@@ -1344,7 +1343,7 @@ struct OutputStreamSftp : public AbstractFileSystem::OutputStreamImpl
{
session_ = getSharedSftpSession(login); //throw SysError
- session_->executeBlocking(L"libssh2_sftp_open", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_open", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) //noexcept!
{
fileHandle_ = ::libssh2_sftp_open(sd.sftpChannel, getLibssh2Path(filePath),
@@ -1445,7 +1444,7 @@ private:
throw SysError(L"Contract error: close() called more than once.");
ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr);
- session_->executeBlocking(L"libssh2_sftp_close", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_close", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_close(fileHandle_); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
@@ -1461,7 +1460,7 @@ private:
ssize_t bytesWritten = 0;
try
{
- session_->executeBlocking(L"libssh2_sftp_write", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_write", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) //noexcept!
{
bytesWritten = ::libssh2_sftp_write(fileHandle_, static_cast<const char*>(buffer), bytesToWrite);
@@ -1469,7 +1468,7 @@ private:
});
if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw SysError(L"libssh2_sftp_write: buffer overflow.");
+ throw SysError(formatSystemError("libssh2_sftp_write", L"", L"Buffer overflow."));
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
catch (const FatalSshError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); } //SSH session corrupted! => caller (will/should) stop using session
@@ -1490,7 +1489,7 @@ private:
try
{
- session_->executeBlocking(L"libssh2_sftp_setstat", //throw SysError, FatalSshError
+ session_->executeBlocking("libssh2_sftp_setstat", //throw SysError, FatalSshError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_setstat(sd.sftpChannel, getLibssh2Path(filePath_), &attribNew); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
@@ -1517,6 +1516,8 @@ class SftpFileSystem : public AbstractFileSystem
public:
SftpFileSystem(const SftpLoginInfo& login) : login_(login) {}
+ const SftpLoginInfo& getLogin() const { return login_; }
+
AfsPath getHomePath() const //throw FileError
{
try
@@ -1541,7 +1542,7 @@ private:
const SftpLoginInfo& rhs = static_cast<const SftpFileSystem&>(afsRhs).login_;
//exactly the type of case insensitive comparison we need for server names!
- const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://msdn.microsoft.com/en-us/library/windows/desktop/ms738519#IDNs
+ const int rv = compareAsciiNoCase(lhs.server, rhs.server); //https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow#IDNs
if (rv != 0)
return rv;
@@ -1558,11 +1559,11 @@ private:
try
{
LIBSSH2_SFTP_ATTRIBUTES attr = {};
- runSftpCommand(login_, L"libssh2_sftp_lstat", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_lstat", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_lstat(sd.sftpChannel, getLibssh2Path(afsPath), &attr); }); //noexcept!
if ((attr.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) == 0)
- throw SysError(L"File attributes not available.");
+ throw SysError(formatSystemError("libssh2_sftp_lstat", L"", L"File attributes not available."));
if (LIBSSH2_SFTP_S_ISLNK(attr.permissions))
return ItemType::SYMLINK;
@@ -1590,7 +1591,7 @@ private:
try
{
//fails if folder is already existing:
- runSftpCommand(login_, L"libssh2_sftp_mkdir", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_mkdir", //throw SysError
[&](const SshSession::Details& sd) //noexcept!
{
return ::libssh2_sftp_mkdir(sd.sftpChannel, getLibssh2Path(afsPath), SFTP_DEFAULT_PERMISSION_FOLDER);
@@ -1607,7 +1608,7 @@ private:
{
try
{
- runSftpCommand(login_, L"libssh2_sftp_unlink", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_unlink", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_unlink(sd.sftpChannel, getLibssh2Path(afsPath)); }); //noexcept!
}
catch (const SysError& e)
@@ -1626,7 +1627,7 @@ private:
int delResult = LIBSSH2_ERROR_NONE;
try
{
- runSftpCommand(login_, L"libssh2_sftp_rmdir", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_rmdir", //throw SysError
[&](const SshSession::Details& sd) { return delResult = ::libssh2_sftp_rmdir(sd.sftpChannel, getLibssh2Path(afsPath)); }); //noexcept!
}
catch (const SysError& e)
@@ -1659,14 +1660,14 @@ private:
const size_t bufSize = 10000;
std::vector<char> buf(bufSize + 1); //ensure buffer is always null-terminated since we don't evaluate the byte count returned by libssh2_sftp_realpath()!
- runSftpCommand(login_, L"libssh2_sftp_realpath", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_realpath", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_realpath(sd.sftpChannel, sftpPath, &buf[0], bufSize); }); //noexcept!
const std::string sftpPathTrg = &buf[0];
if (!startsWith(sftpPathTrg, '/'))
throw SysError(replaceCpy<std::wstring>(L"Invalid path %x.", L"%x", fmtPath(utfTo<std::wstring>(sftpPathTrg))));
- return sanitizeRootRelativePath(utfTo<Zstring>(sftpPathTrg)); //code-reuse! but the sanitize part isn't really needed here...
+ return sanitizeDeviceRelativePath(utfTo<Zstring>(sftpPathTrg)); //code-reuse! but the sanitize part isn't really needed here...
}
AbstractPath getSymlinkResolvedPath(const AfsPath& afsPath) const override //throw FileError
@@ -1685,7 +1686,7 @@ private:
std::string buf(bufSize + 1, '\0'); //ensure buffer is always null-terminated since we don't evaluate the byte count returned by libssh2_sftp_readlink()!
try
{
- runSftpCommand(login_, L"libssh2_sftp_readlink", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_readlink", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_readlink(sd.sftpChannel, getLibssh2Path(afsPath), &buf[0], bufSize); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
@@ -1761,7 +1762,7 @@ private:
try
{
- runSftpCommand(login_, L"libssh2_sftp_rename", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_rename", //throw SysError
[&](const SshSession::Details& sd) //noexcept!
{
/*
@@ -1816,7 +1817,7 @@ private:
LIBSSH2_SFTP_STATVFS fsStats = {};
try
{
- runSftpCommand(login_, L"libssh2_sftp_statvfs", //throw SysError
+ runSftpCommand(login_, "libssh2_sftp_statvfs", //throw SysError
[&](const SshSession::Details& sd) { return ::libssh2_sftp_statvfs(sd.sftpChannel, sftpPath.c_str(), sftpPath.size(), &fsStats); }); //noexcept!
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(getDisplayPath(L"/"))), e.toString()); }
@@ -1845,7 +1846,7 @@ private:
//===========================================================================================================================
-//expects "clean" login data, see condenseToSftpFolderPathPhrase()
+//expects "clean" login data
Zstring concatenateSftpFolderPathPhrase(const SftpLoginInfo& login, const AfsPath& afsPath) //noexcept
{
Zstring port;
@@ -1904,11 +1905,10 @@ AfsPath fff::getSftpHomePath(const SftpLoginInfo& login) //throw FileError
}
-Zstring fff::condenseToSftpFolderPathPhrase(const SftpLoginInfo& login, const Zstring& relPath) //noexcept
+AfsDevice fff::condenseToSftpDevice(const SftpLoginInfo& login) //noexcept
{
+ //clean up input:
SftpLoginInfo loginTmp = login;
-
- //clean-up input:
trim(loginTmp.server);
trim(loginTmp.username);
trim(loginTmp.privateKeyFilePath);
@@ -1922,9 +1922,19 @@ Zstring fff::condenseToSftpFolderPathPhrase(const SftpLoginInfo& login, const Zs
startsWithAsciiNoCase(loginTmp.server, "ftps:" ) ||
startsWithAsciiNoCase(loginTmp.server, "sftp:" ))
loginTmp.server = afterFirst(loginTmp.server, Zstr(':'), IF_MISSING_RETURN_NONE);
- trim(loginTmp.server, true, false, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+ trim(loginTmp.server, true, true, [](Zchar c) { return c == Zstr('/') || c == Zstr('\\'); });
+
+ return makeSharedRef<SftpFileSystem>(loginTmp);
+}
+
+
+SftpLoginInfo fff::extractSftpLogin(const AfsDevice& afsDevice) //noexcept
+{
+ if (const auto sftpDevice = dynamic_cast<const SftpFileSystem*>(&afsDevice.ref()))
+ return sftpDevice->getLogin();
- return concatenateSftpFolderPathPhrase(loginTmp, sanitizeRootRelativePath(relPath));
+ assert(false);
+ return {};
}
@@ -1960,13 +1970,21 @@ int fff::getServerMaxChannelsPerConnection(const SftpLoginInfo& login) //throw F
}
+bool fff::acceptsItemPathPhraseSftp(const Zstring& itemPathPhrase) //noexcept
+{
+ Zstring path = expandMacros(itemPathPhrase); //expand before trimming!
+ trim(path);
+ return startsWithAsciiNoCase(path, sftpPrefix); //check for explicit SFTP path
+}
+
+
//syntax: sftp://[<user>[:<password>]@]<server>[:port]/<relative-path>[|option_name=value]
//
// e.g. sftp://user001:secretpassword@private.example.com:222/mydirectory/
// sftp://user001@private.example.com/mydirectory|con=2|cpc=10|keyfile=%AppData%\id_rsa|pass64=c2VjcmV0cGFzc3dvcmQ
-SftpPathInfo fff::getResolvedSftpPath(const Zstring& folderPathPhrase) //noexcept
+AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept
{
- Zstring pathPhrase = expandMacros(folderPathPhrase); //expand before trimming!
+ Zstring pathPhrase = expandMacros(itemPathPhrase); //expand before trimming!
trim(pathPhrase);
if (startsWithAsciiNoCase(pathPhrase, sftpPrefix))
@@ -1985,7 +2003,7 @@ SftpPathInfo fff::getResolvedSftpPath(const Zstring& folderPathPhrase) //noexcep
auto it = std::find_if(fullPath.begin(), fullPath.end(), [](Zchar c) { return c == '/' || c == '\\'; });
const Zstring serverPort(fullPath.begin(), it);
- const AfsPath serverRelPath = sanitizeRootRelativePath({ it, fullPath.end() });
+ const AfsPath serverRelPath = sanitizeDeviceRelativePath({ it, fullPath.end() });
login.server = beforeLast(serverPort, Zstr(':'), IF_MISSING_RETURN_ALL);
const Zstring port = afterLast(serverPort, Zstr(':'), IF_MISSING_RETURN_NONE);
@@ -2010,20 +2028,6 @@ SftpPathInfo fff::getResolvedSftpPath(const Zstring& folderPathPhrase) //noexcep
else
assert(false);
} //fix "-Wdangling-else"
- return { login, serverRelPath };
-}
-
-bool fff::acceptsItemPathPhraseSftp(const Zstring& itemPathPhrase) //noexcept
-{
- Zstring path = expandMacros(itemPathPhrase); //expand before trimming!
- trim(path);
- return startsWithAsciiNoCase(path, sftpPrefix); //check for explicit SFTP path
-}
-
-
-AbstractPath fff::createItemPathSftp(const Zstring& itemPathPhrase) //noexcept
-{
- const SftpPathInfo& pi = getResolvedSftpPath(itemPathPhrase); //noexcept
- return AbstractPath(makeSharedRef<SftpFileSystem>(pi.login), pi.afsPath);
+ return AbstractPath(makeSharedRef<SftpFileSystem>(login), serverRelPath);
}
diff --git a/FreeFileSync/Source/afs/sftp.h b/FreeFileSync/Source/afs/sftp.h
index bdfcda6f..a400a57f 100644
--- a/FreeFileSync/Source/afs/sftp.h
+++ b/FreeFileSync/Source/afs/sftp.h
@@ -15,17 +15,17 @@ namespace fff
bool acceptsItemPathPhraseSftp(const Zstring& itemPathPhrase); //noexcept
AbstractPath createItemPathSftp(const Zstring& itemPathPhrase); //noexcept
+void sftpInit();
+void sftpTeardown();
+
//-------------------------------------------------------
+
enum class SftpAuthType
{
password,
keyFile,
agent,
};
-//-------------------------------------------------------
-
-void sftpInit();
-void sftpTeardown();
struct SftpLoginInfo
{
@@ -41,17 +41,8 @@ struct SftpLoginInfo
int timeoutSec = 15; //valid range: [1, inf)
int traverserChannelsPerConnection = 1; //valid range: [1, inf)
};
-
-
-struct SftpPathInfo
-{
- SftpLoginInfo login;
- AfsPath afsPath; //server-relative path
-};
-SftpPathInfo getResolvedSftpPath(const Zstring& folderPathPhrase); //noexcept
-
-//expects (potentially messy) user input:
-Zstring condenseToSftpFolderPathPhrase(const SftpLoginInfo& login, const Zstring& relPath); //noexcept
+AfsDevice condenseToSftpDevice(const SftpLoginInfo& login); //noexcept; potentially messy user input
+SftpLoginInfo extractSftpLogin(const AfsDevice& afsDevice); //noexcept
int getServerMaxChannelsPerConnection(const SftpLoginInfo& login); //throw FileError
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index bc78b74e..bb865e27 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -9,6 +9,7 @@
#include <zen/file_access.h>
#include <zen/perf.h>
#include <zen/shutdown.h>
+#include <zen/shell_execute.h>
#include <wx/tooltip.h>
#include <wx/log.h>
#include <wx+/app_main.h>
@@ -66,7 +67,7 @@ bool Application::OnInit()
//=> work around 1: bonus: avoid needless DBus calls: https://developer.gnome.org/gio/stable/running-gio-apps.html
// drawback: missing MTP and network links in folder picker: https://freefilesync.org/forum/viewtopic.php?t=6871
//if (::setenv("GIO_USE_VFS", "local", true /*overwrite*/) != 0)
- // std::cerr << utfTo<std::string>(formatSystemError(L"setenv(GIO_USE_VFS)", errno)) << "\n";
+ // std::cerr << utfTo<std::string>(formatSystemError("setenv(GIO_USE_VFS)", errno)) << "\n";
//
//=> work around 2:
g_vfs_get_default(); //returns unowned GVfs*
@@ -85,8 +86,8 @@ bool Application::OnInit()
(getResourceDirPf() + "Gtk3Styles.css").c_str(), //const gchar* path,
&error); //GError** error
if (error)
- throw SysError(formatSystemError(L"gtk_css_provider_load_from_data", replaceCpy(_("Error Code %x"), L"%x",
- numberTo<std::wstring>(error->code)),
+ throw SysError(formatSystemError("gtk_css_provider_load_from_data",
+ replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)),
utfTo<std::wstring>(error->message)));
::gtk_style_context_add_provider_for_screen(::gdk_screen_get_default(), //GdkScreen* screen,
@@ -101,7 +102,7 @@ bool Application::OnInit()
//Windows User Experience Interaction Guidelines: tool tips should have 5s timeout, info tips no timeout => compromise:
wxToolTip::Enable(true); //yawn, a wxWidgets screw-up: wxToolTip::SetAutoPop is no-op if global tooltip window is not yet constructed: wxToolTip::Enable creates it
- wxToolTip::SetAutoPop(10000); //https://msdn.microsoft.com/en-us/library/windows/desktop/aa511495
+ wxToolTip::SetAutoPop(10000); //https://docs.microsoft.com/en-us/windows/win32/uxguide/ctrl-tooltips-and-infotips
SetAppName(L"FreeFileSync"); //if not set, the default is the executable's name!
@@ -143,9 +144,7 @@ void Application::onEnterEventLoop(wxEvent& event)
{
Disconnect(EVENT_ENTER_EVENT_LOOP, wxEventHandler(Application::onEnterEventLoop), nullptr, this);
- //determine FFS mode of operation
- std::vector<Zstring> commandArgs = getCommandlineArgs(*this);
- launch(commandArgs);
+ launch(getCommandlineArgs(*this)); //determine FFS mode of operation
}
@@ -163,11 +162,11 @@ int Application::OnRun()
const auto titleFmt = copyStringTo<std::wstring>(wxTheApp->GetAppDisplayName()) + SPACED_DASH + _("An exception occurred");
std::cerr << utfTo<std::string>(titleFmt + SPACED_DASH) << e.what() << '\n';
- return FFS_RC_EXCEPTION;
+ return FFS_EXIT_EXCEPTION;
}
//catch (...) -> let it crash and create mini dump!!!
- return returnCode_;
+ return exitCode_;
}
@@ -177,13 +176,13 @@ void Application::onQueryEndSession(wxEvent& event)
mainWin->onQueryEndSession();
//it's futile to try and clean up while the process is in full swing (CRASH!) => just terminate!
//also: avoid wxCloseEvent::Veto() cancelling shutdown when some dialogs receive a close event from the system
- terminateProcess(FFS_RC_ABORTED);
+ terminateProcess(FFS_EXIT_ABORTED);
}
void runGuiMode (const Zstring& globalConfigFile);
void runGuiMode (const Zstring& globalConfigFile, const XmlGuiConfig& guiCfg, const std::vector<Zstring>& cfgFilePaths, bool startComparison);
-void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode);
+void runBatchMode(const Zstring& globalConfigFile, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsExitCode& exitCode);
void showSyntaxHelp();
@@ -203,7 +202,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
//alternative0: std::wcerr: cannot display non-ASCII at all, so why does it exist???
//alternative1: wxSafeShowMessage => NO console output on Debian x86, WTF!
//alternative2: wxMessageBox() => works, but we probably shouldn't block during command line usage
- raiseReturnCode(returnCode_, FFS_RC_ABORTED);
+ raiseExitCode(exitCode_, FFS_EXIT_ABORTED);
};
//parse command line arguments
@@ -402,7 +401,7 @@ void Application::launch(const std::vector<Zstring>& commandArgs)
}
if (!replaceDirectories(batchCfg.mainCfg))
return;
- runBatchMode(globalConfigFilePath, batchCfg, filepath, returnCode_);
+ runBatchMode(globalConfigFilePath, batchCfg, filepath, exitCode_);
}
//GUI mode: single config (ffs_gui *or* ffs_batch)
else
@@ -495,18 +494,18 @@ void showSyntaxHelp()
}
-void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsReturnCode& returnCode)
+void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& batchCfg, const Zstring& cfgFilePath, FfsExitCode& exitCode)
{
const bool showPopupAllowed = !batchCfg.mainCfg.ignoreErrors && batchCfg.batchExCfg.batchErrorHandling == BatchErrorHandling::showPopup;
- auto notifyError = [&](const std::wstring& msg, FfsReturnCode rc)
+ auto notifyError = [&](const std::wstring& msg, FfsExitCode rc)
{
if (showPopupAllowed)
showNotificationDialog(nullptr, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(msg));
else //"exit" or "ignore"
logFatalError(utfTo<std::string>(msg));
- raiseReturnCode(returnCode, rc);
+ raiseExitCode(exitCode, rc);
};
XmlGlobalSettings globalCfg;
@@ -525,7 +524,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
}
catch (const FileError& e)
{
- return notifyError(e.toString(), FFS_RC_ABORTED); //abort sync!
+ return notifyError(e.toString(), FFS_EXIT_ABORTED); //abort sync!
}
}
@@ -535,7 +534,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
}
catch (const FileError& e)
{
- notifyError(e.toString(), FFS_RC_WARNING);
+ notifyError(e.toString(), FFS_EXIT_WARNING);
//continue!
}
@@ -544,7 +543,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
//regular check for program updates -> disabled for batch
//if (batchCfg.showProgress && manualProgramUpdateRequired())
// checkForUpdatePeriodically(globalCfg.lastUpdateCheck);
- //WinInet not working when FFS is running as a service!!! https://support.microsoft.com/en-us/kb/238425
+ //WinInet not working when FFS is running as a service!!! https://support.microsoft.com/en-us/help/238425/info-wininet-not-supported-for-use-in-services
std::set<AbstractPath> logFilePathsToKeep;
@@ -555,15 +554,15 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
//class handling status updates and error messages
BatchStatusHandler statusHandler(!batchCfg.batchExCfg.runMinimized,
- batchCfg.batchExCfg.autoCloseSummary,
extractJobName(cfgFilePath),
- globalCfg.soundFileSyncFinished,
syncStartTime,
batchCfg.mainCfg.ignoreErrors,
- batchCfg.batchExCfg.batchErrorHandling,
batchCfg.mainCfg.automaticRetryCount,
batchCfg.mainCfg.automaticRetryDelay,
- batchCfg.batchExCfg.postSyncAction);
+ globalCfg.soundFileSyncFinished,
+ batchCfg.batchExCfg.autoCloseSummary,
+ batchCfg.batchExCfg.postSyncAction,
+ batchCfg.batchExCfg.batchErrorHandling);
try
{
//inform about (important) non-default global settings
@@ -600,20 +599,34 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
batchCfg.mainCfg.altLogFolderPathPhrase, globalCfg.logfilesMaxAgeDays, globalCfg.logFormat, logFilePathsToKeep,
batchCfg.mainCfg.emailNotifyAddress, batchCfg.mainCfg.emailNotifyCondition); //noexcept
//----------------------------------------------------------------------
+ switch (r.syncResult)
+ {
+ //*INDENT-OFF*
+ case SyncResult::finishedSuccess: raiseExitCode(exitCode, FFS_EXIT_SUCCESS); break;
+ case SyncResult::finishedWarning: raiseExitCode(exitCode, FFS_EXIT_WARNING); break;
+ case SyncResult::finishedError: raiseExitCode(exitCode, FFS_EXIT_ERROR ); break;
+ case SyncResult::aborted: raiseExitCode(exitCode, FFS_EXIT_ABORTED); break;
+ //*INDENT-ON*
+ }
+
+ //email sending, or saving log file failed? at the very least this should affect the exit code:
+ if (r.logStats.fatal > 0 || r.logStats.error > 0)
+ raiseExitCode(exitCode, FFS_EXIT_ERROR);
+ else if (r.logStats.warning > 0)
+ raiseExitCode(exitCode, FFS_EXIT_WARNING);
- raiseReturnCode(returnCode, mapToReturnCode(r.resultStatus));
//update last sync stats for the selected cfg file
for (ConfigFileItem& cfi : globalCfg.gui.mainDlg.cfgFileHistory)
if (equalNativePath(cfi.cfgFilePath, cfgFilePath))
{
- if (r.resultStatus != SyncResult::aborted)
+ if (r.syncResult != SyncResult::aborted)
cfi.lastSyncTime = std::chrono::system_clock::to_time_t(syncStartTime);
assert(!AFS::isNullPath(r.logFilePath));
if (!AFS::isNullPath(r.logFilePath))
{
cfi.logFilePath = r.logFilePath;
- cfi.logResult = r.resultStatus;
+ cfi.logResult = r.syncResult;
}
break;
}
@@ -625,7 +638,7 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
}
catch (const FileError& e)
{
- notifyError(e.toString(), FFS_RC_WARNING);
+ notifyError(e.toString(), FFS_EXIT_WARNING);
}
using FinalRequest = BatchStatusHandler::FinalRequest;
@@ -640,9 +653,9 @@ void runBatchMode(const Zstring& globalConfigFilePath, const XmlBatchConfig& bat
try
{
shutdownSystem(); //throw FileError
- terminateProcess(0 /*exitCode*/); //no point in continuing and saving cfg again in onQueryEndSession() while the OS will kill us anytime!
+ terminateProcess(exitCode); //no point in continuing and saving cfg again in onQueryEndSession() while the OS will kill us anytime!
}
- catch (const FileError& e) { notifyError(e.toString(), FFS_RC_WARNING); }
+ catch (const FileError& e) { notifyError(e.toString(), FFS_EXIT_ERROR); }
break;
}
}
diff --git a/FreeFileSync/Source/application.h b/FreeFileSync/Source/application.h
index a52e6617..0b8c4d79 100644
--- a/FreeFileSync/Source/application.h
+++ b/FreeFileSync/Source/application.h
@@ -28,7 +28,7 @@ private:
void onQueryEndSession(wxEvent& event);
void launch(const std::vector<Zstring>& commandArgs);
- FfsReturnCode returnCode_ = FFS_RC_SUCCESS;
+ FfsExitCode exitCode_ = FFS_EXIT_SUCCESS;
};
}
diff --git a/FreeFileSync/Source/base/dir_lock.cpp b/FreeFileSync/Source/base/dir_lock.cpp
index 5ba444c7..1d087955 100644
--- a/FreeFileSync/Source/base/dir_lock.cpp
+++ b/FreeFileSync/Source/base/dir_lock.cpp
@@ -117,7 +117,7 @@ std::optional<SessionId> getSessionId(ProcessId processId) //throw FileError
const pid_t procSid = ::getsid(processId); //NOT to be confused with "login session", e.g. not stable on OS X!!!
if (procSid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getsid");
+ THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getsid");
return procSid;
}
@@ -148,11 +148,11 @@ LockInformation getLockInfoFromCurrentProcess() //throw FileError
//wxGetFullHostName() is a performance killer and can hang for some users, so don't touch!
std::vector<char> buffer(10000);
if (::gethostname(&buffer[0], buffer.size()) != 0)
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"gethostname");
+ THROW_LAST_FILE_ERROR(_("Cannot get process information."), "gethostname");
lockInfo.computerName = osName + ' ' + &buffer[0] + '.';
if (::getdomainname(&buffer[0], buffer.size()) != 0)
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getdomainname");
+ THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getdomainname");
lockInfo.computerName += &buffer[0];
lockInfo.processId = ::getpid(); //never fails
@@ -284,14 +284,14 @@ uint64_t getLockFileSize(const Zstring& filePath) //throw FileError, ErrorFileNo
return fileInfo.st_size;
if (errno == ENOENT)
- throw ErrorFileNotExisting(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), formatSystemError(L"stat", errno));
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
+ throw ErrorFileNotExisting(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), formatSystemError("stat", errno));
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat");
}
void waitOnDirLock(const Zstring& lockFilePath, const DirLockCallback& notifyStatus /*throw X*/, std::chrono::milliseconds cbInterval) //throw FileError
{
- std::wstring infoMsg = _("Waiting while directory is locked:") + L' ' + fmtPath(lockFilePath);
+ std::wstring infoMsg = _("Waiting while directory is in use:") + L' ' + fmtPath(lockFilePath);
if (notifyStatus) notifyStatus(infoMsg); //throw X
@@ -399,7 +399,7 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError
const mode_t oldMask = ::umask(0); //important: we want the lock file to have exactly the permissions specified
ZEN_ON_SCOPE_EXIT(::umask(oldMask));
- //O_EXCL contains a race condition on NFS file systems: http://linux.die.net/man/2/open
+ //O_EXCL contains a race condition on NFS file systems: https://linux.die.net/man/2/open
const int hFile = ::open(lockFilePath.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666
if (hFile == -1)
@@ -407,7 +407,7 @@ bool tryLock(const Zstring& lockFilePath) //throw FileError
if (errno == EEXIST)
return false;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockFilePath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(lockFilePath)), "open");
}
ZEN_ON_SCOPE_FAIL(try { removeFilePlain(lockFilePath); }
catch (FileError&) {});
diff --git a/FreeFileSync/Source/base/resolve_path.cpp b/FreeFileSync/Source/base/resolve_path.cpp
index f2737069..6c7105b2 100644
--- a/FreeFileSync/Source/base/resolve_path.cpp
+++ b/FreeFileSync/Source/base/resolve_path.cpp
@@ -47,38 +47,52 @@ std::optional<Zstring> getEnvironmentVar(const Zstring& name)
Zstring resolveRelativePath(const Zstring& relativePath)
{
assert(runningMainThread()); //GetFullPathName() is documented to NOT be thread-safe!
+ /* MSDN: "Multithreaded applications and shared library code should not use the GetFullPathName function
+ and should avoid using relative path names.
+ The current directory state written by the SetCurrentDirectory function is stored as a global variable in each process, */
- //http://linux.die.net/man/2/path_resolution
- if (!startsWith(relativePath, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/'
+ if (relativePath.empty())
+ return relativePath;
+
+ Zstring pathTmp = relativePath;
+ //https://linux.die.net/man/2/path_resolution
+ if (!startsWith(pathTmp, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/'
{
- /*
- basic support for '~': strictly speaking this is a shell-layer feature, so "realpath()" won't handle it
- http://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
-
- http://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory
- should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows
- the user to modify their notion of "the home directory" during a login session.
- */
- if (startsWith(relativePath, "~/") || relativePath == "~")
+ /* basic support for '~': strictly speaking this is a shell-layer feature, so "realpath()" won't handle it
+ https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
+
+ https://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory
+ should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows
+ the user to modify their notion of "the home directory" during a login session. */
+ if (startsWith(pathTmp, "~/") || pathTmp == "~")
{
- std::optional<Zstring> homeDir = getEnvironmentVar("HOME");
- if (!homeDir)
- return relativePath; //error! no further processing!
-
- if (startsWith(relativePath, "~/"))
- return appendSeparator(*homeDir) + afterFirst(relativePath, '/', IF_MISSING_RETURN_NONE);
- else //relativePath == "~"
- return *homeDir;
+ if (const std::optional<Zstring> homeDir = getEnvironmentVar("HOME"))
+ {
+ if (startsWith(pathTmp, "~/"))
+ pathTmp = appendSeparator(*homeDir) + afterFirst(pathTmp, '/', IF_MISSING_RETURN_NONE);
+ else //pathTmp == "~"
+ pathTmp = *homeDir;
+ }
+ //else: error! no further processing!
}
-
- //we cannot use ::realpath() since it resolves *existing* relative paths only!
- if (char* dirPath = ::getcwd(nullptr, 0))
+ else
{
- ZEN_ON_SCOPE_EXIT(::free(dirPath));
- return appendSeparator(dirPath) + relativePath;
+ //we cannot use ::realpath() since it resolves *existing* relative paths only!
+ if (char* dirPath = ::getcwd(nullptr, 0))
+ {
+ ZEN_ON_SCOPE_EXIT(::free(dirPath));
+ pathTmp = appendSeparator(dirPath) + pathTmp;
+ }
}
}
- return relativePath;
+ //get rid of some cruft (just like GetFullPathName())
+ replace(pathTmp, "/./", '/');
+ if (endsWith(pathTmp, "/."))
+ pathTmp.pop_back(); //keep the "/" => consider pathTmp == "/."
+
+ //what about "/../"? might be relative to symlinks => preserve!
+
+ return pathTmp;
}
@@ -245,18 +259,14 @@ Zstring fff::getResolvedFilePath(const Zstring& pathPhrase) //noexcept
path = expandVolumeName(path); //may block for slow USB sticks and idle HDDs!
- if (path.empty()) //an empty string would later be resolved as "\"; this is not desired
- return Zstring();
- /*
- need to resolve relative paths:
- WINDOWS:
- - \\?\-prefix requires absolute names
- - Volume Shadow Copy: volume name needs to be part of each file path
- - file icon buffer (at least for extensions that are actually read from disk, like "exe")
- - Use of relative path names is not thread safe! (e.g. SHFileOperation)
- WINDOWS/LINUX:
- - detection of dependent directories, e.g. "\" and "C:\test"
- */
+ /* need to resolve relative paths:
+ WINDOWS:
+ - \\?\-prefix requires absolute names
+ - Volume Shadow Copy: volume name needs to be part of each file path
+ - file icon buffer (at least for extensions that are actually read from disk, like "exe")
+ - Use of relative path names is not thread safe! (e.g. SHFileOperation)
+ WINDOWS/LINUX:
+ - detection of dependent directories, e.g. "\" and "C:\test" */
path = resolveRelativePath(path);
//remove trailing slash, unless volume root:
diff --git a/FreeFileSync/Source/base/structures.cpp b/FreeFileSync/Source/base/structures.cpp
index 1e6758ac..8452f3f2 100644
--- a/FreeFileSync/Source/base/structures.cpp
+++ b/FreeFileSync/Source/base/structures.cpp
@@ -17,16 +17,17 @@ using namespace zen;
using namespace fff;
-std::wstring fff::getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arrowLeft, const wchar_t* arrowRight, const wchar_t* angleRight)
+//use in sync log files where users expect ANSI: https://freefilesync.org/forum/viewtopic.php?t=4647
+std::wstring fff::getVariantNameForLog(DirectionConfig::Variant var)
{
switch (var)
{
case DirectionConfig::TWO_WAY:
- return arrowLeft + _("Two way") + arrowRight;
+ return _("Two way") + L" <->";
case DirectionConfig::MIRROR:
- return _("Mirror") + arrowRight;
+ return _("Mirror") + L" ->";
case DirectionConfig::UPDATE:
- return _("Update") + angleRight;
+ return _("Update") + L" >";
case DirectionConfig::CUSTOM:
return _("Custom");
}
@@ -35,13 +36,6 @@ std::wstring fff::getVariantNameImpl(DirectionConfig::Variant var, const wchar_t
}
-//use in sync log files where users expect ANSI: https://freefilesync.org/forum/viewtopic.php?t=4647
-std::wstring fff::getVariantNameForLog(DirectionConfig::Variant var)
-{
- return getVariantNameImpl(var, L"<-", L"->", L">");
-}
-
-
DirectionSet fff::extractDirections(const DirectionConfig& cfg)
{
DirectionSet output;
diff --git a/FreeFileSync/Source/base/structures.h b/FreeFileSync/Source/base/structures.h
index 11e98948..88d7eb54 100644
--- a/FreeFileSync/Source/base/structures.h
+++ b/FreeFileSync/Source/base/structures.h
@@ -148,7 +148,6 @@ bool detectMovedFilesEnabled (const DirectionConfig& cfg);
DirectionSet extractDirections(const DirectionConfig& cfg); //get sync directions: DON'T call for DirectionConfig::TWO_WAY!
-std::wstring getVariantNameImpl(DirectionConfig::Variant var, const wchar_t* arrowLeft, const wchar_t* arrowRight, const wchar_t* angleRight);
std::wstring getVariantNameForLog(DirectionConfig::Variant var);
inline
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index 38ad1771..c64915a9 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -451,11 +451,11 @@ void flushFileBuffers(const Zstring& nativeFilePath) //throw FileError
{
const int fileHandle = ::open(nativeFilePath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC);
if (fileHandle == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(nativeFilePath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(nativeFilePath)), "open");
ZEN_ON_SCOPE_EXIT(::close(fileHandle));
if (::fsync(fileHandle) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(nativeFilePath)), L"fsync");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(nativeFilePath)), "fsync");
}
@@ -2398,7 +2398,7 @@ void fff::synchronize(const std::chrono::system_clock::time_point& syncStartTime
msg += L'\n' + utfTo<std::wstring>(item.relPath) + L": " + item.msg;
if (makeUnsigned(conflictCount) > conflictPreview.size())
- msg += L"\n [...] " + replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", conflictCount), //%x used as plural form placeholder!
+ msg += L"\n [...] " + replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", conflictCount), //%x used as plural form placeholder!
L"%y", formatNumber(conflictPreview.size()));
}
}
diff --git a/FreeFileSync/Source/base_tools.cpp b/FreeFileSync/Source/base_tools.cpp
index 3a7dabbd..97f6cc65 100644
--- a/FreeFileSync/Source/base_tools.cpp
+++ b/FreeFileSync/Source/base_tools.cpp
@@ -30,18 +30,28 @@ std::wstring fff::getVariantName(CompareVariant var)
std::wstring fff::getVariantName(DirectionConfig::Variant var)
{
- const wchar_t* arrowLeft = L"\u25C4 "; //black triangle pointer
- const wchar_t* arrowRight = L" \u25BA"; //
- const wchar_t* angleRight = L" \uFF1E"; //fullwidth greater-than
- //const wchar_t arrowLeft [] = L"\u2190 "; //unicode arrows -> too small
- //const wchar_t arrowRight[] = L" \u2192"; //
+ //https://www.key-shortcut.com/en/writing-systems/35-symbols/arrows/
+ //*INDENT-OFF*
+ const wchar_t* arrowLeft = L"\u25C4 "; //◄
+ const wchar_t* arrowRight = L" \u25BA"; //►
+ const wchar_t* angleRight = L" \uFF1E"; //>
+ //alternatives: ←, → (too small) ⬅, ⮕ (right one not generally available)
if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
{
arrowLeft = L"\u25BA "; //not mirrored automatically: Windows/Linux Unicode bug!?
arrowRight = L" \u25C4"; //
}
- return getVariantNameImpl(var, arrowLeft, arrowRight, angleRight);
+ switch (var)
+ {
+ case DirectionConfig::TWO_WAY: return arrowLeft + _("Two way") + arrowRight;
+ case DirectionConfig::MIRROR: return _("Mirror") + arrowRight;
+ case DirectionConfig::UPDATE: return _("Update") + angleRight;
+ case DirectionConfig::CUSTOM: return _("Custom");
+ }
+ //*INDENT-ON*
+ assert(false);
+ return _("Error");
}
diff --git a/FreeFileSync/Source/config.cpp b/FreeFileSync/Source/config.cpp
index 120ad466..4bbb7120 100644
--- a/FreeFileSync/Source/config.cpp
+++ b/FreeFileSync/Source/config.cpp
@@ -21,7 +21,7 @@ using namespace fff; //functionally needed for correct overload resolution!!!
namespace
{
//-------------------------------------------------------------------------------------------------------------------------------
-const int XML_FORMAT_GLOBAL_CFG = 16; //2020-01-30
+const int XML_FORMAT_GLOBAL_CFG = 17; //2020-04-15
const int XML_FORMAT_SYNC_CFG = 15; //2020-01-30
//-------------------------------------------------------------------------------------------------------------------------------
}
@@ -1840,58 +1840,13 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
if (cfg.gui.commandHistoryMax <= 8)
cfg.gui.commandHistoryMax = XmlGlobalSettings().gui.commandHistoryMax;
- //external applications
- //TODO: remove old parameter after migration! 2016-05-28
- if (inGui["ExternalApplications"])
- {
- inGui["ExternalApplications"](cfg.gui.externalApps);
- if (cfg.gui.externalApps.empty()) //who knows, let's repair some old failed data migrations
- cfg.gui.externalApps = XmlGlobalSettings().gui.externalApps;
- else
- {
- }
- }
+
+ //TODO: remove old parameter after migration! 2018-01-16
+ if (formatVer < 7)
+ ; //reset this old crap
else
- {
- //TODO: remove old parameter after migration! 2018-01-16
- if (formatVer < 7)
- {
- std::vector<std::pair<std::wstring, Zstring>> extApps;
- if (inGui["ExternalApps"](extApps))
- {
- cfg.gui.externalApps.clear();
- for (const auto& [description, cmdLine] : extApps)
- cfg.gui.externalApps.push_back({ description, cmdLine });
- }
- }
- else
- inGui["ExternalApps"](cfg.gui.externalApps);
- }
+ inGui["ExternalApps"](cfg.gui.externalApps);
- //TODO: remove macro migration after some time! 2016-06-30
- if (formatVer < 3)
- for (ExternalApp& item : cfg.gui.externalApps)
- {
- replace(item.cmdLine, Zstr("%item2_path%"), Zstr("%item_path2%"));
- replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%"));
- replace(item.cmdLine, Zstr("%item2_folder%"), Zstr("%folder_path2%"));
-
- replace(item.cmdLine, Zstr("explorer /select, \"%item_path%\""), Zstr("explorer /select, \"%local_path%\""));
- replace(item.cmdLine, Zstr("\"%item_path%\""), Zstr("\"%local_path%\""));
- replace(item.cmdLine, Zstr("xdg-open \"%item_path%\""), Zstr("xdg-open \"%local_path%\""));
- replace(item.cmdLine, Zstr("open -R \"%item_path%\""), Zstr("open -R \"%local_path%\""));
- replace(item.cmdLine, Zstr("open \"%item_path%\""), Zstr("open \"%local_path%\""));
-
- if (contains(makeUpperCopy(item.cmdLine), Zstr("WINMERGEU.EXE")) ||
- contains(makeUpperCopy(item.cmdLine), Zstr("PSPAD.EXE")))
- {
- replace(item.cmdLine, Zstr("%item_path%"), Zstr("%local_path%"));
- replace(item.cmdLine, Zstr("%item_path2%"), Zstr("%local_path2%"));
- }
- }
- //TODO: remove macro migration after some time! 2016-07-18
- for (ExternalApp& item : cfg.gui.externalApps)
- replace(item.cmdLine, Zstr("%item_folder%"), Zstr("%folder_path%"));
//TODO: remove after migration! 2019-11-30
if (formatVer < 15)
for (ExternalApp& item : cfg.gui.externalApps)
@@ -1900,6 +1855,7 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& cfg, int formatVer)
replace(item.cmdLine, Zstr("%folder_path2%"), Zstr("%parent_path2%"));
}
+
//last update check
inGui["LastOnlineCheck" ](cfg.gui.lastUpdateCheck);
inGui["LastOnlineVersion"](cfg.gui.lastOnlineVersion);
diff --git a/FreeFileSync/Source/config.h b/FreeFileSync/Source/config.h
index e76d4770..72bc3990 100644
--- a/FreeFileSync/Source/config.h
+++ b/FreeFileSync/Source/config.h
@@ -206,8 +206,8 @@ struct XmlGlobalSettings
wxString guiPerspectiveLast; //used by wxAuiManager
} mainDlg;
- Zstring defaultExclusionFilter = "/.Trash-*/" "\n"
- "/.recycle/";
+ Zstring defaultExclusionFilter = "*/.Trash-*/" "\n"
+ "*/.recycle/";
size_t folderHistoryMax = 20;
std::vector<Zstring> versioningFolderHistory;
@@ -221,10 +221,11 @@ struct XmlGlobalSettings
std::vector<ExternalApp> externalApps
{
- //default external app descriptions will be translated "on the fly"!!!
- //CONTRACT: first entry will be used for [Enter] or mouse double-click!
- { L"Browse directory", Zstr("xdg-open \"%parent_path%\"") },
- { L"Open with default application", Zstr("xdg-open \"%local_path%\"") },
+ /* CONTRACT: first entry: show item in file browser
+
+ default external app descriptions will be translated "on the fly"!!! */
+ { L"Browse directory", "xdg-open \"%parent_path%\"" },
+ { L"Open with default application", "xdg-open \"%local_path%\"" },
//mark for extraction: _("Browse directory") Linux doesn't use the term "folder"
};
diff --git a/FreeFileSync/Source/log_file.cpp b/FreeFileSync/Source/log_file.cpp
index c89d4403..0b6e25c2 100644
--- a/FreeFileSync/Source/log_file.cpp
+++ b/FreeFileSync/Source/log_file.cpp
@@ -11,7 +11,6 @@
#include <wx/datetime.h>
#include "ffs_paths.h"
#include "afs/concrete.h"
-
using namespace zen;
using namespace fff;
using AFS = AbstractFileSystem;
@@ -32,7 +31,7 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
headerLine += (headerLine.empty() ? "" : " + ") + utfTo<std::string>(jobName);
if (!headerLine.empty())
- headerLine += " ";
+ headerLine += ' ';
const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(s.startTime)); //returns empty string on failure
headerLine += utfTo<std::string>(formatTime(formatDateTag, tc) + Zstr(" [") + formatTime(formatTimeTag, tc) + Zstr(']'));
@@ -40,7 +39,7 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
//assemble summary box
std::vector<std::string> summary;
summary.emplace_back();
- summary.push_back(tabSpace + utfTo<std::string>(getSyncResultLabel(s.resultStatus)));
+ summary.push_back(tabSpace + utfTo<std::string>(getSyncResultLabel(s.syncResult)));
summary.emplace_back();
const ErrorLog::Stats logCount = log.getStats();
@@ -90,7 +89,7 @@ std::string generateLogHeaderTxt(const ProcessSummary& s, const ErrorLog& log, i
break;
}
if (logFailTotal > previewCount)
- output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder!
+ output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logFailTotal), //%x used as plural form placeholder!
L"%y", formatNumber(previewCount))) + '\n';
output += std::string(SEPARATION_LINE_LEN, '_') + "\n\n\n";
}
@@ -104,7 +103,7 @@ std::string generateLogFooterTxt(const std::wstring& logFilePath, int logItemsTo
std::string output;
if (logItemsTotal > logItemsPreviewMax)
- output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder!
+ output += " [...] " + utfTo<std::string>(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logItemsTotal), //%x used as plural form placeholder!
L"%y", formatNumber(logItemsPreviewMax))) + '\n';
return output += '\n' + std::string(SEPARATION_LINE_LEN, '_') + '\n' +
@@ -114,7 +113,7 @@ std::string generateLogFooterTxt(const std::wstring& logFilePath, int logItemsTo
(!cm.model .empty() ? L" - " + cm.model : L"") +
(!cm.vendor.empty() ? L" - " + cm.vendor : L"") + L'\n' +
- _("Log file") + L": " + logFilePath) + '\n';
+ _("Log file:") + L' ' + logFilePath) + '\n';
}
@@ -185,7 +184,7 @@ std::wstring generateLogTitle(const ProcessSummary& s)
if (!jobNamesFmt.empty())
title += jobNamesFmt + L' ';
- switch (s.resultStatus)
+ switch (s.syncResult)
{
case SyncResult::finishedSuccess: title += utfTo<std::wstring>("\xe2\x9c\x94" "\xef\xb8\x8f"); break; //✔️
case SyncResult::finishedWarning: title += utfTo<std::wstring>("\xe2\x9a\xa0" "\xef\xb8\x8f"); break; //⚠️
@@ -228,7 +227,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
htmlTxt(formatTime(formatDateTag, tc)) + " &nbsp;" + htmlTxt(formatTime(formatTimeTag, tc)) + "</span></div>\n";
std::string resultsStatusImage;
- switch (s.resultStatus)
+ switch (s.syncResult)
{
case SyncResult::finishedSuccess: resultsStatusImage = "result-succes.png"; break;
case SyncResult::finishedWarning: resultsStatusImage = "result-warning.png"; break;
@@ -239,7 +238,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
<div style="margin:10px 0; display:inline-block; border-radius:7px; background:#f8f8f8; box-shadow:1px 1px 4px #888; overflow:hidden;">
<div style="background-color:white; border-bottom:1px solid #AAA; font-size:larger; padding:10px;">
<img src="https://freefilesync.org/images/log/)" + resultsStatusImage + R"(" width="32" height="32" alt="" style="vertical-align:middle;">
- <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getSyncResultLabel(s.resultStatus)) + R"(</span>
+ <span style="font-weight:600; vertical-align:middle;">)" + htmlTxt(getSyncResultLabel(s.syncResult)) + R"(</span>
</div>
<table role="presentation" class="summary-table" style="border-spacing:0; margin-left:10px; padding:5px 10px;">)";
@@ -314,7 +313,7 @@ std::string generateLogHeaderHtml(const ProcessSummary& s, const ErrorLog& log,
)";
if (logFailTotal > previewCount)
output += R"( <div><span style="font-weight:600; padding:0 10px;">[&hellip;]</span>)" +
- htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logFailTotal), //%x used as plural form placeholder!
+ htmlTxt(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logFailTotal), //%x used as plural form placeholder!
L"%y", formatNumber(previewCount))) + "</div>\n";
output += R"( <div style="border-bottom: 1px solid #AAA; margin: 5px 0;"></div><br>
@@ -337,7 +336,7 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsT
if (logItemsTotal > logItemsPreviewMax)
output += R"( <div><span style="font-weight:600; padding:0 10px;">[&hellip;]</span>)" +
- htmlTxt(replaceCpy(_P("Showing %y of 1 row", "Showing %y of %x rows", logItemsTotal), //%x used as plural form placeholder!
+ htmlTxt(replaceCpy(_P("Showing %y of 1 item", "Showing %y of %x items", logItemsTotal), //%x used as plural form placeholder!
L"%y", formatNumber(logItemsPreviewMax))) + "</div>\n";
return output += R"( <br>
@@ -351,7 +350,7 @@ std::string generateLogFooterHtml(const std::wstring& logFilePath, int logItemsT
(!cm.vendor.empty() ? " &ndash; " + htmlTxt(cm.vendor) : "") + R"(</span>
</div>
<div style="font-size:small;">
- <img src="https://freefilesync.org/images/log/log.png" width="24" height="24" alt=")" + htmlTxt(_("Log file")) + R"(:" style="vertical-align:middle;">
+ <img src="https://freefilesync.org/images/log/log.png" width="24" height="24" alt=")" + htmlTxt(_("Log file:")) + R"(" style="vertical-align:middle;">
<span style="font-family: Consolas,'Courier New',Courier,monospace; vertical-align:middle;">)" + htmlTxt(logFilePath) + R"(</span>
</div>
</body>
@@ -540,12 +539,28 @@ Zstring fff::getDefaultLogFolderPath() { return getConfigDirPathPf() + Zstr("Log
//"Backup FreeFileSync 2013-09-15 015052.123.html"
//"Backup FreeFileSync 2013-09-15 015052.123 [Error].html"
+//"Backup FreeFileSync + RealTimeSync 2013-09-15 015052.123 [Error].log"
AbstractPath fff::generateLogFilePath(LogFileFormat logFormat, const ProcessSummary& summary, const Zstring& altLogFolderPathPhrase /*optional*/)
{
//const std::string colon = "\xcb\xb8"; //="modifier letter raised colon" => regular colon is forbidden in file names on Windows and OS X
//=> too many issues, most notably cmd.exe is not Unicode-aware: https://freefilesync.org/forum/viewtopic.php?t=1679
- //assemble logfile name
+ Zstring jobNamesFmt;
+ if (!summary.jobNames.empty())
+ {
+ for (const std::wstring& jobName : summary.jobNames)
+ if (const Zstring jobNameZ = utfTo<Zstring>(jobName);
+ jobNamesFmt.size() + jobNameZ.size() > 200)
+ {
+ jobNamesFmt += Zstr("[...] + "); //avoid hitting file system name length limitations: "lpMaximumComponentLength is commonly 255 characters"
+ break; //https://freefilesync.org/forum/viewtopic.php?t=7113
+ }
+ else
+ jobNamesFmt += jobNameZ + Zstr(" + ");
+
+ jobNamesFmt.resize(jobNamesFmt.size() - 3);
+ }
+
const TimeComp tc = getLocalTime(std::chrono::system_clock::to_time_t(summary.startTime));
if (tc == TimeComp())
throw FileError(L"Failed to determine current time: " + numberTo<std::wstring>(summary.startTime.time_since_epoch().count()));
@@ -553,21 +568,9 @@ AbstractPath fff::generateLogFilePath(LogFileFormat logFormat, const ProcessSumm
const auto timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(summary.startTime.time_since_epoch()).count() % 1000;
assert(std::chrono::duration_cast<std::chrono::seconds>(summary.startTime.time_since_epoch()).count() == std::chrono::system_clock::to_time_t(summary.startTime));
- Zstring logFileName;
- if (!summary.jobNames.empty())
- {
- for (const std::wstring& jobName : summary.jobNames)
- logFileName += utfTo<Zstring>(jobName) + Zstr(" + ");
- logFileName.resize(logFileName.size() - 2);
- }
-
- logFileName += formatTime(Zstr("%Y-%m-%d %H%M%S"), tc) +
- Zstr(".") + printNumber<Zstring>(Zstr("%03d"), static_cast<int>(timeMs)); //[ms] should yield a fairly unique name
- static_assert(TIME_STAMP_LENGTH == 21);
-
const std::wstring failStatus = [&]
{
- switch (summary.resultStatus)
+ switch (summary.syncResult)
{
case SyncResult::finishedSuccess: break;
case SyncResult::finishedWarning: return _("Warning");
@@ -576,6 +579,15 @@ AbstractPath fff::generateLogFilePath(LogFileFormat logFormat, const ProcessSumm
}
return std::wstring();
}();
+ //------------------------------------------------------------------
+
+ Zstring logFileName = jobNamesFmt;
+ if (!logFileName.empty())
+ logFileName += Zstr(' ');
+
+ logFileName += formatTime(Zstr("%Y-%m-%d %H%M%S"), tc) +
+ Zstr(".") + printNumber<Zstring>(Zstr("%03d"), static_cast<int>(timeMs)); //[ms] should yield a fairly unique name
+ static_assert(TIME_STAMP_LENGTH == 21);
if (!failStatus.empty())
logFileName += STATUS_BEGIN_TOKEN + utfTo<Zstring>(failStatus) + STATUS_END_TOKEN;
diff --git a/FreeFileSync/Source/return_codes.h b/FreeFileSync/Source/return_codes.h
index ad9318c6..c1d30d5a 100644
--- a/FreeFileSync/Source/return_codes.h
+++ b/FreeFileSync/Source/return_codes.h
@@ -12,18 +12,18 @@
namespace fff
{
-enum FfsReturnCode //as returned after process exit
+enum FfsExitCode //as returned on process exit
{
- FFS_RC_SUCCESS = 0,
- FFS_RC_WARNING,
- FFS_RC_ERROR,
- FFS_RC_ABORTED,
- FFS_RC_EXCEPTION,
+ FFS_EXIT_SUCCESS = 0,
+ FFS_EXIT_WARNING,
+ FFS_EXIT_ERROR,
+ FFS_EXIT_ABORTED,
+ FFS_EXIT_EXCEPTION,
};
inline
-void raiseReturnCode(FfsReturnCode& rc, FfsReturnCode rcProposed)
+void raiseExitCode(FfsExitCode& rc, FfsExitCode rcProposed)
{
if (rc < rcProposed)
rc = rcProposed;
@@ -40,37 +40,16 @@ enum class SyncResult
inline
-FfsReturnCode mapToReturnCode(SyncResult syncStatus)
+std::wstring getSyncResultLabel(SyncResult syncResult)
{
- switch (syncStatus)
+ switch (syncResult)
{
- case SyncResult::finishedSuccess:
- return FFS_RC_SUCCESS;
- case SyncResult::finishedWarning:
- return FFS_RC_WARNING;
- case SyncResult::finishedError:
- return FFS_RC_ERROR;
- case SyncResult::aborted:
- return FFS_RC_ABORTED;
- }
- assert(false);
- return FFS_RC_ABORTED;
-}
-
-
-inline
-std::wstring getSyncResultLabel(SyncResult resultStatus)
-{
- switch (resultStatus)
- {
- case SyncResult::finishedSuccess:
- return _("Completed successfully");
- case SyncResult::finishedWarning:
- return _("Completed with warnings");
- case SyncResult::finishedError:
- return _("Completed with errors");
- case SyncResult::aborted:
- return _("Stopped");
+ //*INDENT-OFF*
+ case SyncResult::finishedSuccess: return _("Completed successfully");
+ case SyncResult::finishedWarning: return _("Completed with warnings");
+ case SyncResult::finishedError: return _("Completed with errors");
+ case SyncResult::aborted: return _("Stopped");
+ //*INDENT-ON*
}
assert(false);
return std::wstring();
diff --git a/FreeFileSync/Source/status_handler.h b/FreeFileSync/Source/status_handler.h
index 9058033b..7c22f2cd 100644
--- a/FreeFileSync/Source/status_handler.h
+++ b/FreeFileSync/Source/status_handler.h
@@ -8,11 +8,11 @@
#define STATUS_HANDLER_H_81704805908341534
#include <vector>
-#include <chrono>
-#include <thread>
-#include <string>
-#include <zen/i18n.h>
-#include <zen/basic_math.h>
+//#include <chrono>
+//#include <thread>
+//#include <string>
+//#include <zen/i18n.h>
+//#include <zen/basic_math.h>
#include "base/process_callback.h"
#include "return_codes.h"
@@ -72,7 +72,7 @@ struct Statistics
struct ProcessSummary
{
std::chrono::system_clock::time_point startTime;
- SyncResult resultStatus = SyncResult::aborted;
+ SyncResult syncResult = SyncResult::aborted;
std::vector<std::wstring> jobNames; //may be empty
ProgressStats statsProcessed;
ProgressStats statsTotal;
@@ -169,24 +169,6 @@ private:
std::optional<AbortTrigger> abortRequested_;
};
-
-//------------------------------------------------------------------------------------------
-
-inline
-void delayAndCountDown(const std::wstring& operationName, std::chrono::seconds delay, const std::function<void(const std::wstring& msg)>& notifyStatus)
-{
- assert(notifyStatus && !zen::endsWith(operationName, L"."));
-
- const auto delayUntil = std::chrono::steady_clock::now() + delay;
- for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
- {
- const auto timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(delayUntil - now).count();
- if (notifyStatus)
- notifyStatus(operationName + L"... " + _P("1 sec", "%x sec", numeric::integerDivideRoundUp(timeMs, 1000)));
-
- std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2);
- }
-}
}
#endif //STATUS_HANDLER_H_81704805908341534
diff --git a/FreeFileSync/Source/ui/batch_status_handler.cpp b/FreeFileSync/Source/ui/batch_status_handler.cpp
index 40098bca..034946c4 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.cpp
+++ b/FreeFileSync/Source/ui/batch_status_handler.cpp
@@ -9,29 +9,33 @@
#include <zen/shutdown.h>
#include <wx+/popup_dlg.h>
#include <wx/app.h>
+#include <wx/sound.h>
#include "../afs/concrete.h"
#include "../base/resolve_path.h"
#include "../log_file.h"
+#include "status_handler_impl.h"
using namespace zen;
using namespace fff;
BatchStatusHandler::BatchStatusHandler(bool showProgress,
- bool autoCloseDialog,
const std::wstring& jobName,
- const Zstring& soundFileSyncComplete,
const std::chrono::system_clock::time_point& startTime,
bool ignoreErrors,
- BatchErrorHandling batchErrorHandling,
size_t automaticRetryCount,
std::chrono::seconds automaticRetryDelay,
- PostSyncAction postSyncAction) :
- batchErrorHandling_(batchErrorHandling),
+ const Zstring& soundFileSyncComplete,
+ bool autoCloseDialog,
+ PostSyncAction postSyncAction,
+ BatchErrorHandling batchErrorHandling) :
+ jobName_(jobName),
+ startTime_(startTime),
automaticRetryCount_(automaticRetryCount),
automaticRetryDelay_(automaticRetryDelay),
+ soundFileSyncComplete_(soundFileSyncComplete),
progressDlg_(SyncProgressDialog::create([this] { userRequestAbort(); }, *this, nullptr /*parentWindow*/, showProgress, autoCloseDialog,
-startTime, { jobName }, soundFileSyncComplete, ignoreErrors, automaticRetryCount, [&]
+{ jobName }, startTime, ignoreErrors, automaticRetryCount, [&]
{
switch (postSyncAction)
{
@@ -45,8 +49,7 @@ startTime, { jobName }, soundFileSyncComplete, ignoreErrors, automaticRetryCount
assert(false);
return PostSyncAction2::none;
}())),
-jobName_(jobName),
- startTime_(startTime)
+batchErrorHandling_(batchErrorHandling)
{
//ATTENTION: "progressDlg_" is an unmanaged resource!!! However, at this point we already consider construction complete! =>
//ZEN_ON_SCOPE_FAIL( cleanup(); ); //destructor call would lead to member double clean-up!!!
@@ -70,7 +73,7 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
progressDlg_->timerSetStatus(false /*active*/); //keep correct summary window stats considering count down timer, system sleep
//determine post-sync status irrespective of further errors during tear-down
- const SyncResult resultStatus = [&]
+ const SyncResult syncResult = [&]
{
if (getAbortStatus())
{
@@ -88,11 +91,11 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
return SyncResult::finishedSuccess;
}();
- assert(resultStatus == SyncResult::aborted || currentPhase() == ProcessPhase::synchronizing);
+ assert(syncResult == SyncResult::aborted || currentPhase() == ProcessPhase::synchronizing);
const ProcessSummary summary
{
- startTime_, resultStatus, { jobName_ },
+ startTime_, syncResult, { jobName_ },
getStatsCurrent(),
getStatsTotal (),
totalTime
@@ -101,83 +104,66 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase);
//e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log
- if (const Zstring cmdLine = trimCpy(postSyncCommand);
- !cmdLine.empty())
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't run post sync command!
- else if (postSyncCondition == PostSyncCondition::COMPLETION ||
- (postSyncCondition == PostSyncCondition::ERRORS) == (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError))
- try
- {
- ////----------------------------------------------------------------------
- //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath));
- ////----------------------------------------------------------------------
- const Zstring cmdLineExp = expandMacros(cmdLine);
- errorLog_.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(cmdLineExp), MSG_TYPE_INFO);
- shellExecute(cmdLineExp, ExecutionType::async, false /*hideConsole*/); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
- }
-
- //---------------------------- save log file ------------------------------
auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} };
- if (const std::string notifyEmail = trimCpy(emailNotifyAddress);
- !notifyEmail.empty())
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't send email notification!
- else if (emailNotifyCondition == ResultsNotification::always ||
- (emailNotifyCondition == ResultsNotification::errorWarning && (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError ||
- resultStatus == SyncResult::finishedWarning)) ||
- (emailNotifyCondition == ResultsNotification::errorOnly && (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError)))
- try
- {
- sendLogAsEmail(notifyEmail, summary, errorLog_, logFilePath, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
- }
-
- try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
- {
- //do NOT use tryReportingError()! saving log files should not be cancellable!
- saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
-
- //----------------- post sync action ------------------------
- auto mayRunAfterCountDown = [&](const std::wstring& operationName)
- {
- auto notifyStatusThrowOnCancel = [&](const std::wstring& msg)
- {
- try { updateStatus(msg); /*throw AbortProcess*/ }
- catch (AbortProcess&)
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- throw;
- }
- };
-
- if (progressDlg_->getWindowIfVisible())
- try
- {
- delayAndCountDown(operationName, std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
- }
- catch (AbortProcess&) { return false; }
-
- return true;
- };
-
bool autoClose = false;
FinalRequest finalRequest = FinalRequest::none;
+ bool suspend = false;
if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't run post sync action!
+ ; /* user cancelled => don't run post sync command
+ => don't send email notification
+ => don't run post sync action
+ => don't play sound notification */
else
+ {
+ //--------------------- post sync command ----------------------
+ if (const Zstring cmdLine = trimCpy(postSyncCommand);
+ !cmdLine.empty())
+ if (postSyncCondition == PostSyncCondition::COMPLETION ||
+ (postSyncCondition == PostSyncCondition::ERRORS) == (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError))
+ ////----------------------------------------------------------------------
+ //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath));
+ ////----------------------------------------------------------------------
+ runCommandAndLogErrors(expandMacros(cmdLine), errorLog_);
+
+ //--------------------- email notification ----------------------
+ if (const std::string notifyEmail = trimCpy(emailNotifyAddress);
+ !notifyEmail.empty())
+ if (emailNotifyCondition == ResultsNotification::always ||
+ (emailNotifyCondition == ResultsNotification::errorWarning && (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError ||
+ syncResult == SyncResult::finishedWarning)) ||
+ (emailNotifyCondition == ResultsNotification::errorOnly && (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError)))
+ try
+ {
+ sendLogAsEmail(notifyEmail, summary, errorLog_, logFilePath, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+
+ //--------------------- post sync actions ----------------------
+ auto mayRunAfterCountDown = [&](const std::wstring& operationName)
+ {
+ if (progressDlg_->getWindowIfVisible())
+ try
+ {
+ auto notifyStatusThrowOnCancel = [&](const std::wstring& msg)
+ {
+ try { updateStatus(msg); /*throw AbortProcess*/ }
+ catch (AbortProcess&)
+ {
+ if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
+ throw;
+ }
+ };
+ delayAndCountDown(operationName, std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
+ }
+ catch (AbortProcess&) { return false; }
+
+ return true;
+ };
switch (progressDlg_->getOptionPostSyncAction())
{
case PostSyncAction2::none:
@@ -188,12 +174,10 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
break;
case PostSyncAction2::sleep:
if (mayRunAfterCountDown(_("System: Sleep")))
- try
- {
- suspendSystem(); //throw FileError
- autoClose = progressDlg_->getOptionAutoCloseDialog();
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+ {
+ autoClose = progressDlg_->getOptionAutoCloseDialog();
+ suspend = true;
+ }
break;
case PostSyncAction2::shutdown:
if (mayRunAfterCountDown(_("System: Shut down")))
@@ -203,6 +187,38 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
}
break;
}
+
+ //--------------------- sound notification ----------------------
+ if (!autoClose) //only play when showing results dialog
+ if (!soundFileSyncComplete_.empty())
+ {
+ //wxWidgets shows modal error dialog by default => NO!
+ wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership!
+ ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget));
+
+ wxSound::Play(utfTo<wxString>(soundFileSyncComplete_), wxSOUND_ASYNC);
+ }
+ //if (::GetForegroundWindow() != GetHWND())
+ // RequestUserAttention(); -> probably too much since task bar is already colorized with Taskbar::STATUS_ERROR or STATUS_NORMAL
+ }
+
+ //--------------------- save log file ----------------------
+ try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
+ {
+ //do NOT use tryReportingError()! saving log files should not be cancellable!
+ saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+ //----------------------------------------------------------
+
+
+ if (suspend) //...*before* results dialog is shown
+ try
+ {
+ suspendSystem(); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+
if (switchToGuiRequested_) //-> avoid recursive yield() calls, thous switch not before ending batch mode
{
autoClose = true;
@@ -213,10 +229,10 @@ BatchStatusHandler::Result BatchStatusHandler::reportResults(const Zstring& post
progressDlg_->destroy(autoClose,
true /*restoreParentFrame: n/a here*/,
- resultStatus, errorLogFinal);
+ syncResult, errorLogFinal);
progressDlg_ = nullptr;
- return { resultStatus, finalRequest, logFilePath };
+ return { syncResult, errorLogFinal.ref().getStats(), finalRequest, logFilePath };
}
@@ -300,7 +316,7 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& ms
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess
return ProcessCallback::retry;
}
diff --git a/FreeFileSync/Source/ui/batch_status_handler.h b/FreeFileSync/Source/ui/batch_status_handler.h
index 2e59ac7e..c748720c 100644
--- a/FreeFileSync/Source/ui/batch_status_handler.h
+++ b/FreeFileSync/Source/ui/batch_status_handler.h
@@ -21,15 +21,15 @@ class BatchStatusHandler : public StatusHandler
{
public:
BatchStatusHandler(bool showProgress,
- bool autoCloseDialog,
const std::wstring& jobName, //should not be empty for a batch job!
- const Zstring& soundFileSyncComplete,
const std::chrono::system_clock::time_point& startTime,
bool ignoreErrors,
- BatchErrorHandling batchErrorHandling,
size_t automaticRetryCount,
std::chrono::seconds automaticRetryDelay,
- PostSyncAction postSyncAction); //noexcept!!
+ const Zstring& soundFileSyncComplete,
+ bool autoCloseDialog,
+ PostSyncAction postSyncAction,
+ BatchErrorHandling batchErrorHandling); //noexcept!!
~BatchStatusHandler();
void initNewPhase (int itemsTotal, int64_t bytesTotal, ProcessPhase phaseID) override; //
@@ -49,7 +49,8 @@ public:
};
struct Result
{
- SyncResult resultStatus;
+ SyncResult syncResult;
+ zen::ErrorLog::Stats logStats;
FinalRequest finalRequest;
AbstractPath logFilePath;
};
@@ -58,14 +59,16 @@ public:
const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!!
private:
- bool switchToGuiRequested_ = false;
- const BatchErrorHandling batchErrorHandling_;
- zen::ErrorLog errorLog_; //list of non-resolved errors and warnings
+ const std::wstring jobName_;
+ const std::chrono::system_clock::time_point startTime_;
const size_t automaticRetryCount_;
const std::chrono::seconds automaticRetryDelay_;
+ const Zstring soundFileSyncComplete_;
+
SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler!
- const std::wstring jobName_;
- const std::chrono::system_clock::time_point startTime_;
+ zen::ErrorLog errorLog_; //list of non-resolved errors and warnings
+ const BatchErrorHandling batchErrorHandling_;
+ bool switchToGuiRequested_ = false;
};
}
diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp
index fca67704..b262c1f4 100644
--- a/FreeFileSync/Source/ui/cfg_grid.cpp
+++ b/FreeFileSync/Source/ui/cfg_grid.cpp
@@ -585,7 +585,7 @@ private:
try
{
if (std::optional<Zstring> nativePath = AFS::getNativeItemPath(item->cfgItem.logFilePath))
- openWithDefaultApplication(*nativePath); //throw FileError
+ openWithDefaultApp(*nativePath); //throw FileError
else
assert(false);
assert(!AFS::isNullPath(item->cfgItem.logFilePath)); //see getRowMouseHover()
diff --git a/FreeFileSync/Source/ui/file_grid.cpp b/FreeFileSync/Source/ui/file_grid.cpp
index 93708754..cc194246 100644
--- a/FreeFileSync/Source/ui/file_grid.cpp
+++ b/FreeFileSync/Source/ui/file_grid.cpp
@@ -484,36 +484,48 @@ private:
rectTmp.x += extent.GetWidth();
rectTmp.width -= extent.GetWidth();
};
+ auto drawFilePath = [&](std::wstring path)
+ {
+ //path components should follow the app layout direction and are NOT a single piece of text!
+ //caveat: add Bidi support only during rendering and not in getValue() or AFS::getDisplayPath(): e.g. support "open file in Explorer"
+ assert(!contains(path, slashBidi_) && !contains(path, bslashBidi_));
+ replace(path, L"/", slashBidi_);
+ replace(path, L"\\", bslashBidi_);
+ drawTextBlock(path);
+ };
- const std::wstring cellValue = getValue(row, colType);
+ const std::wstring& cellValue = getValue(row, colType);
switch (static_cast<ColumnTypeRim>(colType))
{
case ColumnTypeRim::ITEM_PATH:
{
if (!iconMgr_)
- drawTextBlock(cellValue);
+ drawFilePath(cellValue);
else
{
auto it = cellValue.end();
while (it != cellValue.begin()) //reverse iteration: 1. check 2. decrement 3. evaluate
{
--it;
- if (*it == '\\' || *it == '/')
+ if (*it == L'\\' || *it == L'/')
{
++it;
break;
}
}
- const std::wstring pathPrefix(cellValue.begin(), it);
- const std::wstring itemName(it, cellValue.end());
+ /*const */std::wstring pathPrefix(cellValue.begin(), it);
+ const std::wstring itemName(it, cellValue.end());
+
+ if (!pathPrefix.empty())
+ pathPrefix.pop_back(); //don't really need the trailing slash
// Partitioning:
// __________________________________________________
// | gap | path prefix | gap | icon | gap | item name |
// --------------------------------------------------
if (!pathPrefix.empty())
- drawTextBlock(pathPrefix);
+ drawFilePath(pathPrefix);
//draw file icon
rectTmp.x += gridGap_;
@@ -576,7 +588,7 @@ private:
rectTmp.x += iconSize;
rectTmp.width -= iconSize;
- drawTextBlock(itemName);
+ drawFilePath(itemName);
}
}
break;
@@ -736,18 +748,22 @@ private:
AFS::getDisplayPath(fsObj->getAbstractPath<side>()) :
utfTo<std::wstring>(fsObj->getRelativePath<side>());
+ assert(!contains(toolTip, slashBidi_) && !contains(toolTip, bslashBidi_));
+ replace(toolTip, L"/", slashBidi_);
+ replace(toolTip, L"\\", bslashBidi_);
+
visitFSObject(*fsObj, [](const FolderPair& folder) {},
[&](const FilePair& file)
{
toolTip += L'\n' +
- _("Size:") + L' ' + zen::formatFilesizeShort(file.getFileSize<side>()) + L'\n' +
- _("Date:") + L' ' + zen::formatUtcToLocalTime(file.getLastWriteTime<side>());
+ _("Size:") + L' ' + formatFilesizeShort(file.getFileSize<side>()) + L'\n' +
+ _("Date:") + L' ' + formatUtcToLocalTime(file.getLastWriteTime<side>());
},
[&](const SymlinkPair& symlink)
{
toolTip += L'\n' +
- _("Date:") + L' ' + zen::formatUtcToLocalTime(symlink.getLastWriteTime<side>());
+ _("Date:") + L' ' + formatUtcToLocalTime(symlink.getLastWriteTime<side>());
});
}
return toolTip;
@@ -760,6 +776,10 @@ private:
std::vector<char> failedLoads_; //effectively a vector<bool> of size "number of rows"
std::optional<wxBitmap> renderBuf_; //avoid costs of recreating this temporary variable
+
+ const std::wstring slashBidi_ = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? RTL_MARK : LTR_MARK) + std::wstring() + L"/";
+ const std::wstring bslashBidi_ = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? RTL_MARK : LTR_MARK) + std::wstring() + L"\\";
+ //no need for LTR/RTL marks on both sides: text follows main direction if slash is between two strong characters with different directions
};
diff --git a/FreeFileSync/Source/ui/folder_selector.cpp b/FreeFileSync/Source/ui/folder_selector.cpp
index 8e2037ba..27e9f42d 100644
--- a/FreeFileSync/Source/ui/folder_selector.cpp
+++ b/FreeFileSync/Source/ui/folder_selector.cpp
@@ -142,8 +142,11 @@ void FolderSelector::onItemPathDropped(FileDropEvent& event)
if (!droppedPathsFilter_ || droppedPathsFilter_(itemPaths))
{
- auto fmtShellPath = [](const Zstring& shellItemPath)
+ auto fmtShellPath = [](Zstring shellItemPath)
{
+ if (endsWith(shellItemPath, Zstr(' '))) //prevent createAbstractPath() from trimming legit trailing blank!
+ shellItemPath += FILE_NAME_SEPARATOR;
+
const AbstractPath itemPath = createAbstractPath(shellItemPath);
try
{
@@ -208,6 +211,7 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event)
}
}
+ Zstring shellItemPath;
wxDirDialog dirPicker(parent_, _("Select a folder"), utfTo<wxString>(defaultFolderPath)); //put modal wxWidgets dialogs on stack: creating on freestore leads to memleak!
//-> following doesn't seem to do anything at all! still "Show hidden" is available as a context menu option:
@@ -215,9 +219,14 @@ void FolderSelector::onSelectFolder(wxCommandEvent& event)
if (dirPicker.ShowModal() != wxID_OK)
return;
- const Zstring newFolderPathPhrase = utfTo<Zstring>(dirPicker.GetPath());
+ shellItemPath = utfTo<Zstring>(dirPicker.GetPath());
+ if (endsWith(shellItemPath, Zstr(' '))) //prevent createAbstractPath() from trimming legit trailing blank!
+ shellItemPath += FILE_NAME_SEPARATOR;
+
+ //make sure FFS-specific explicit MTP-syntax is applied!
+ const Zstring newFolderPathPhrase = AFS::getInitPathPhrase(createAbstractPath(shellItemPath)); //noexcept
- setFolderPathPhrase(newFolderPathPhrase, &folderComboBox_, folderComboBox_, staticText_);
+ setPath(newFolderPathPhrase);
//notify action invoked by user
wxCommandEvent dummy(EVENT_ON_FOLDER_SELECTED);
@@ -237,7 +246,7 @@ void FolderSelector::onSelectAltFolder(wxCommandEvent& event)
if (showCloudSetupDialog(parent_, folderPathPhrase, parallelOps, get(parallelOpsDisabledReason)) != ReturnSmallDlg::BUTTON_OKAY)
return;
- setFolderPathPhrase(folderPathPhrase, &folderComboBox_, folderComboBox_, staticText_);
+ setPath(folderPathPhrase);
if (setDeviceParallelOps_)
setDeviceParallelOps_(folderPathPhrase, parallelOps);
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index 78092e06..124bce47 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -851,8 +851,6 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const
bSizer287->Add( m_bpButtonViewTypeSyncAction, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5 );
m_bpButtonViewContext = new wxBitmapButton( m_panelViewFilter, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), wxBU_AUTODRAW|0 );
- m_bpButtonViewContext->SetToolTip( _("dummy") );
-
bSizer287->Add( m_bpButtonViewContext, 0, wxRIGHT|wxEXPAND, 5 );
@@ -4597,9 +4595,18 @@ OptionsDlgGenerated::OptionsDlgGenerated( wxWindow* parent, wxWindowID id, const
wxBoxSizer* bSizer289;
bSizer289 = new wxBoxSizer( wxHORIZONTAL );
+ wxBoxSizer* bSizer2971;
+ bSizer2971 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_bitmapConsole = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 );
+ bSizer2971->Add( m_bitmapConsole, 0, wxALIGN_CENTER_VERTICAL, 5 );
+
m_staticText85 = new wxStaticText( m_panel39, wxID_ANY, _("Customize context menu:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText85->Wrap( -1 );
- bSizer289->Add( m_staticText85, 1, wxRIGHT, 5 );
+ bSizer2971->Add( m_staticText85, 0, wxALIGN_CENTER_VERTICAL|wxLEFT, 5 );
+
+
+ bSizer289->Add( bSizer2971, 1, 0, 5 );
wxFlexGridSizer* fgSizer25;
fgSizer25 = new wxFlexGridSizer( 0, 2, 0, 10 );
diff --git a/FreeFileSync/Source/ui/gui_generated.h b/FreeFileSync/Source/ui/gui_generated.h
index 07867a58..00e51eb1 100644
--- a/FreeFileSync/Source/ui/gui_generated.h
+++ b/FreeFileSync/Source/ui/gui_generated.h
@@ -1057,6 +1057,7 @@ protected:
wxButton* m_buttonSelectSoundSyncDone;
wxBitmapButton* m_bpButtonPlaySyncDone;
wxStaticLine* m_staticline3611;
+ wxStaticBitmap* m_bitmapConsole;
wxStaticText* m_staticText85;
wxStaticText* m_staticText174;
wxStaticText* m_staticText175;
diff --git a/FreeFileSync/Source/ui/gui_status_handler.cpp b/FreeFileSync/Source/ui/gui_status_handler.cpp
index 6ad01cd2..aa635da2 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.cpp
+++ b/FreeFileSync/Source/ui/gui_status_handler.cpp
@@ -8,12 +8,14 @@
#include <zen/shell_execute.h>
#include <zen/shutdown.h>
#include <wx/app.h>
+#include <wx/sound.h>
#include <wx/wupdlock.h>
#include <wx+/popup_dlg.h>
#include "main_dlg.h"
#include "../afs/concrete.h"
#include "../base/resolve_path.h"
#include "../log_file.h"
+#include "status_handler_impl.h"
using namespace zen;
using namespace fff;
@@ -141,7 +143,7 @@ StatusHandlerTemporaryPanel::Result StatusHandlerTemporaryPanel::reportResults()
const auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - startTime_);
//determine post-sync status irrespective of further errors during tear-down
- const SyncResult resultStatus = [&]
+ const SyncResult syncResult = [&]
{
if (getAbortStatus())
{
@@ -160,7 +162,7 @@ StatusHandlerTemporaryPanel::Result StatusHandlerTemporaryPanel::reportResults()
const ProcessSummary summary
{
- startTime_, resultStatus, {} /*jobName*/,
+ startTime_, syncResult, {} /*jobName*/,
getStatsCurrent(),
getStatsTotal (),
totalTime
@@ -230,7 +232,7 @@ ProcessCallback::Response StatusHandlerTemporaryPanel::reportError(const std::ws
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess
return ProcessCallback::retry;
}
@@ -332,19 +334,20 @@ void StatusHandlerTemporaryPanel::OnAbortCompare(wxCommandEvent& event)
//########################################################################################################
StatusHandlerFloatingDialog::StatusHandlerFloatingDialog(wxFrame* parentDlg,
+ const std::vector<std::wstring>& jobNames,
const std::chrono::system_clock::time_point& startTime,
bool ignoreErrors,
size_t automaticRetryCount,
std::chrono::seconds automaticRetryDelay,
- const std::vector<std::wstring>& jobNames,
const Zstring& soundFileSyncComplete,
bool& autoCloseDialog) :
+ jobNames_(jobNames),
+ startTime_(startTime),
+ automaticRetryCount_(automaticRetryCount),
+ automaticRetryDelay_(automaticRetryDelay),
+ soundFileSyncComplete_(soundFileSyncComplete),
progressDlg_(SyncProgressDialog::create([this] { userRequestAbort(); }, *this, parentDlg, true /*showProgress*/, autoCloseDialog,
-startTime, jobNames, soundFileSyncComplete, ignoreErrors, automaticRetryCount, PostSyncAction2::none)),
- automaticRetryCount_(automaticRetryCount),
- automaticRetryDelay_(automaticRetryDelay),
- jobNames_(jobNames),
- startTime_(startTime),
+jobNames, startTime, ignoreErrors, automaticRetryCount, PostSyncAction2::none)),
autoCloseDialogOut_(autoCloseDialog) {}
@@ -365,7 +368,7 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
progressDlg_->timerSetStatus(false /*active*/); //keep correct summary window stats considering count down timer, system sleep
//determine post-sync status irrespective of further errors during tear-down
- const SyncResult resultStatus = [&]
+ const SyncResult syncResult = [&]
{
if (getAbortStatus())
{
@@ -384,11 +387,11 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
return SyncResult::finishedSuccess;
}();
- assert(resultStatus == SyncResult::aborted || currentPhase() == ProcessPhase::synchronizing);
+ assert(syncResult == SyncResult::aborted || currentPhase() == ProcessPhase::synchronizing);
const ProcessSummary summary
{
- startTime_, resultStatus, jobNames_,
+ startTime_, syncResult, jobNames_,
getStatsCurrent(),
getStatsTotal (),
totalTime
@@ -397,83 +400,67 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
const AbstractPath logFilePath = generateLogFilePath(logFormat, summary, altLogFolderPathPhrase);
//e.g. %AppData%\FreeFileSync\Logs\Backup FreeFileSync 2013-09-15 015052.123 [Error].log
- if (const Zstring cmdLine = trimCpy(postSyncCommand);
- !cmdLine.empty())
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't run post sync command!
- else if (postSyncCondition == PostSyncCondition::COMPLETION ||
- (postSyncCondition == PostSyncCondition::ERRORS) == (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError))
- try
- {
- ////----------------------------------------------------------------------
- //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath));
- ////----------------------------------------------------------------------
- const Zstring cmdLineExp = expandMacros(cmdLine);
- errorLog_.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(cmdLineExp), MSG_TYPE_INFO);
- shellExecute(cmdLineExp, ExecutionType::async, false /*hideConsole*/); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
- }
-
- //---------------------------- save log file ------------------------------
auto notifyStatusNoThrow = [&](const std::wstring& msg) { try { updateStatus(msg); /*throw AbortProcess*/ } catch (AbortProcess&) {} };
- if (const std::string notifyEmail = trimCpy(emailNotifyAddress);
- !notifyEmail.empty())
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't send email notification!
- else if (emailNotifyCondition == ResultsNotification::always ||
- (emailNotifyCondition == ResultsNotification::errorWarning && (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError ||
- resultStatus == SyncResult::finishedWarning)) ||
- (emailNotifyCondition == ResultsNotification::errorOnly && (resultStatus == SyncResult::aborted ||
- resultStatus == SyncResult::finishedError)))
- try
- {
- sendLogAsEmail(notifyEmail, summary, errorLog_, logFilePath, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
- }
+ bool autoClose = false;
+ FinalRequest finalRequest = FinalRequest::none;
+ bool suspend = false;
- try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
+ if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
+ ; /* user cancelled => don't run post sync command
+ => don't send email notification
+ => don't run post sync action
+ => don't play sound notification */
+ else
{
- //do NOT use tryReportingError()! saving log files should not be cancellable!
- saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+ //--------------------- post sync command ----------------------
+ if (const Zstring cmdLine = trimCpy(postSyncCommand);
+ !cmdLine.empty())
+ if (postSyncCondition == PostSyncCondition::COMPLETION ||
+ (postSyncCondition == PostSyncCondition::ERRORS) == (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError))
+ ////----------------------------------------------------------------------
+ //::wxSetEnv(L"logfile_path", AFS::getDisplayPath(logFilePath));
+ ////----------------------------------------------------------------------
+ runCommandAndLogErrors(expandMacros(cmdLine), errorLog_);
+
+ //--------------------- email notification ----------------------
+ if (const std::string notifyEmail = trimCpy(emailNotifyAddress);
+ !notifyEmail.empty())
+ if (emailNotifyCondition == ResultsNotification::always ||
+ (emailNotifyCondition == ResultsNotification::errorWarning && (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError ||
+ syncResult == SyncResult::finishedWarning)) ||
+ (emailNotifyCondition == ResultsNotification::errorOnly && (syncResult == SyncResult::aborted ||
+ syncResult == SyncResult::finishedError)))
+ try
+ {
+ sendLogAsEmail(notifyEmail, summary, errorLog_, logFilePath, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
- //----------------- post sync action ------------------------
- auto mayRunAfterCountDown = [&](const std::wstring& operationName)
- {
- auto notifyStatusThrowOnCancel = [&](const std::wstring& msg)
+ //--------------------- post sync actions ----------------------
+ auto mayRunAfterCountDown = [&](const std::wstring& operationName)
{
- try { updateStatus(msg); /*throw AbortProcess*/ }
- catch (AbortProcess&)
- {
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- throw;
- }
- };
-
- if (progressDlg_->getWindowIfVisible())
- try
- {
- delayAndCountDown(operationName, std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
- }
- catch (AbortProcess&) { return false; }
-
- return true;
- };
+ if (progressDlg_->getWindowIfVisible())
+ try
+ {
+ auto notifyStatusThrowOnCancel = [&](const std::wstring& msg)
+ {
+ try { updateStatus(msg); /*throw AbortProcess*/ }
+ catch (AbortProcess&)
+ {
+ if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
+ throw;
+ }
+ };
+ delayAndCountDown(operationName, std::chrono::seconds(5), notifyStatusThrowOnCancel); //throw AbortProcess
+ }
+ catch (AbortProcess&) { return false; }
- bool autoClose = false;
- FinalRequest finalRequest = FinalRequest::none;
+ return true;
+ };
- if (getAbortStatus() && *getAbortStatus() == AbortTrigger::user)
- ; //user cancelled => don't run post sync action!
- else
switch (progressDlg_->getOptionPostSyncAction())
{
case PostSyncAction2::none:
@@ -485,12 +472,10 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
break;
case PostSyncAction2::sleep:
if (mayRunAfterCountDown(_("System: Sleep")))
- try
- {
- suspendSystem(); //throw FileError
- autoClose = progressDlg_->getOptionAutoCloseDialog();
- }
- catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+ {
+ autoClose = progressDlg_->getOptionAutoCloseDialog();
+ suspend = true;
+ }
break;
case PostSyncAction2::shutdown:
if (mayRunAfterCountDown(_("System: Shut down")))
@@ -501,12 +486,44 @@ StatusHandlerFloatingDialog::Result StatusHandlerFloatingDialog::reportResults(c
break;
}
+ //--------------------- sound notification ----------------------
+ if (!autoClose) //only play when showing results dialog
+ if (!soundFileSyncComplete_.empty())
+ {
+ //wxWidgets shows modal error dialog by default => NO!
+ wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership!
+ ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget));
+
+ wxSound::Play(utfTo<wxString>(soundFileSyncComplete_), wxSOUND_ASYNC);
+ }
+ //if (::GetForegroundWindow() != GetHWND())
+ // RequestUserAttention(); -> probably too much since task bar is already colorized with Taskbar::STATUS_ERROR or STATUS_NORMAL
+ }
+
+ //--------------------- save log file ----------------------
+ try //create not before destruction: 1. avoid issues with FFS trying to sync open log file 2. include status in log file name without extra rename
+ {
+ //do NOT use tryReportingError()! saving log files should not be cancellable!
+ saveLogFile(logFilePath, summary, errorLog_, logfilesMaxAgeDays, logFormat, logFilePathsToKeep, notifyStatusNoThrow); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+ //----------------------------------------------------------
+
+
+ if (suspend) //...*before* results dialog is shown
+ try
+ {
+ suspendSystem(); //throw FileError
+ }
+ catch (const FileError& e) { errorLog_.logMsg(e.toString(), MSG_TYPE_ERROR); }
+
+
auto errorLogFinal = makeSharedRef<const ErrorLog>(std::move(errorLog_));
autoCloseDialogOut_ = //output parameter owned by SyncProgressDialog (evaluate *after* user closed the results dialog)
progressDlg_->destroy(autoClose,
finalRequest == FinalRequest::none /*restoreParentFrame*/,
- resultStatus, errorLogFinal).autoCloseDialog;
+ syncResult, errorLogFinal).autoCloseDialog;
progressDlg_ = nullptr;
return { summary, errorLogFinal, finalRequest, logFilePath };
@@ -570,7 +587,7 @@ ProcessCallback::Response StatusHandlerFloatingDialog::reportError(const std::ws
if (retryNumber < automaticRetryCount_)
{
errorLog_.logMsg(msg + L"\n-> " + _("Automatic retry"), MSG_TYPE_INFO);
- delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
+ delayAndCountDown(_("Automatic retry") + (automaticRetryCount_ <= 1 ? L"" : L' ' + numberTo<std::wstring>(retryNumber + 1) + L"/" + numberTo<std::wstring>(automaticRetryCount_)),
automaticRetryDelay_, [&](const std::wstring& statusMsg) { this->updateStatus(_("Error") + L": " + statusMsg); }); //throw AbortProcess
return ProcessCallback::retry;
}
diff --git a/FreeFileSync/Source/ui/gui_status_handler.h b/FreeFileSync/Source/ui/gui_status_handler.h
index 5f671a82..2a9e00d2 100644
--- a/FreeFileSync/Source/ui/gui_status_handler.h
+++ b/FreeFileSync/Source/ui/gui_status_handler.h
@@ -60,11 +60,11 @@ class StatusHandlerFloatingDialog : public StatusHandler
{
public:
StatusHandlerFloatingDialog(wxFrame* parentDlg,
+ const std::vector<std::wstring>& jobNames,
const std::chrono::system_clock::time_point& startTime,
bool ignoreErrors,
size_t automaticRetryCount,
std::chrono::seconds automaticRetryDelay,
- const std::vector<std::wstring>& jobNames,
const Zstring& soundFileSyncComplete,
bool& autoCloseDialog); //noexcept!
~StatusHandlerFloatingDialog();
@@ -96,12 +96,14 @@ public:
const std::string& emailNotifyAddress, ResultsNotification emailNotifyCondition); //noexcept!!
private:
- SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler!
- zen::ErrorLog errorLog_;
- const size_t automaticRetryCount_;
- const std::chrono::seconds automaticRetryDelay_;
const std::vector<std::wstring> jobNames_;
const std::chrono::system_clock::time_point startTime_;
+ const size_t automaticRetryCount_;
+ const std::chrono::seconds automaticRetryDelay_;
+ const Zstring soundFileSyncComplete_;
+
+ SyncProgressDialog* progressDlg_; //managed to have the same lifetime as this handler!
+ zen::ErrorLog errorLog_;
bool& autoCloseDialogOut_; //owned by SyncProgressDialog
};
}
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 77c4c1c9..0896185c 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -57,6 +57,8 @@ using namespace fff;
namespace
{
const size_t EXT_APP_MASS_INVOKE_THRESHOLD = 10; //more is likely a user mistake (Explorer uses limit of 15)
+const size_t EXT_APP_MAX_TOTAL_WAIT_TIME_MS = 1000;
+
const int TOP_BUTTON_OPTIMAL_WIDTH_DIP = 170;
const std::chrono::milliseconds LAST_USED_CFG_EXISTENCE_CHECK_TIME_MAX(500);
const std::chrono::milliseconds FILE_GRID_POST_UPDATE_DELAY(400);
@@ -700,7 +702,7 @@ MainDialog::MainDialog(const Zstring& globalConfigFilePath,
this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuUpdateAvailable));
menu->Append(newItem); //pass ownership
- const std::wstring blackStar = utfTo<std::wstring>("\xE2\x98\x85"); //"BLACK STAR"
+ const std::wstring blackStar = utfTo<std::wstring>("★");
m_menubar->Append(menu, blackStar + L' ' + replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(globalSettings.gui.lastOnlineVersion)) + L' ' + blackStar);
}
@@ -1466,6 +1468,7 @@ void collectNonNativeFiles(const std::vector<FileSystemObject*>& selectedRows, c
template <SelectedSide side>
void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError
+ bool openWithDefaultAppRequested,
const std::vector<FileSystemObject*>& selection,
const TempFileBuffer& tempFileBuf)
{
@@ -1500,17 +1503,32 @@ void invokeCommandLine(const Zstring& commandLinePhrase, //throw FileError
if (localPath .empty()) localPath = replaceCpy(utfTo<Zstring>(L"<" + _("Local path not available for %x.") + L">"), Zstr("%x"), itemPath );
if (localPath2.empty()) localPath2 = replaceCpy(utfTo<Zstring>(L"<" + _("Local path not available for %x.") + L">"), Zstr("%x"), itemPath2);
- Zstring command = commandLinePhrase;
- replace(command, Zstr("%item_path%"), itemPath);
- replace(command, Zstr("%item_path2%"), itemPath2);
- replace(command, Zstr("%local_path%"), localPath);
- replace(command, Zstr("%local_path2%"), localPath2);
- replace(command, Zstr("%item_name%"), itemName);
- replace(command, Zstr("%item_name2%"), itemName2);
- replace(command, Zstr("%parent_path%"), folderPath);
- replace(command, Zstr("%parent_path2%"), folderPath2);
-
- shellExecute(command, selection.size() > EXT_APP_MASS_INVOKE_THRESHOLD ? ExecutionType::sync : ExecutionType::async, false/*hideConsole*/); //throw FileError
+ Zstring cmdLine = expandMacros(commandLinePhrase);
+ replace(cmdLine, Zstr("%item_path%"), itemPath);
+ replace(cmdLine, Zstr("%item_path2%"), itemPath2);
+ replace(cmdLine, Zstr("%local_path%"), localPath);
+ replace(cmdLine, Zstr("%local_path2%"), localPath2);
+ replace(cmdLine, Zstr("%item_name%"), itemName);
+ replace(cmdLine, Zstr("%item_name2%"), itemName2);
+ replace(cmdLine, Zstr("%parent_path%"), folderPath);
+ replace(cmdLine, Zstr("%parent_path2%"), folderPath2);
+
+ if (openWithDefaultAppRequested) //not strictly needed, but: 1. better error reporting (Windows) 2. not async => avoid zombies (Linux/macOS)
+ openWithDefaultApp(localPath); //throw FileError
+ else
+ try
+ {
+ std::optional<int> timeoutMs;
+ if (selection.size() <= EXT_APP_MASS_INVOKE_THRESHOLD)
+ timeoutMs = EXT_APP_MAX_TOTAL_WAIT_TIME_MS / EXT_APP_MASS_INVOKE_THRESHOLD; //run async, but give consoleExecute() some "time to fail"
+ //else: run synchronously
+
+ if (const auto [exitCode, output] = consoleExecute(cmdLine, timeoutMs); //throw SysError, SysErrorTimeOut
+ exitCode != 0)
+ throw zen::SysError(formatSystemError(utfTo<std::string>(commandLinePhrase), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
+ }
+ catch (SysErrorTimeOut&) {} //child process not failed yet => probably fine :>
+ catch (const zen::SysError& e) { throw FileError(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)), e.toString()); }
}
}
}
@@ -1521,10 +1539,20 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool
const std::vector<FileSystemObject*>& selectionRight)
{
const XmlGlobalSettings::Gui defaultCfg;
- const bool openFileBrowserRequested = !defaultCfg.externalApps.empty() && defaultCfg.externalApps[0].cmdLine == commandLinePhrase;
+ const bool showInFileBrowserRequested = defaultCfg.externalApps.size() >= 1 && defaultCfg.externalApps[0].cmdLine == commandLinePhrase;
+ const bool openWithDefaultAppRequested = defaultCfg.externalApps.size() >= 2 && defaultCfg.externalApps[1].cmdLine == commandLinePhrase;
+
+ auto openFolderInFileBrowser = [this](const AbstractPath& folderPath)
+ {
+ try
+ {
+ openWithDefaultApp(utfTo<Zstring>(AFS::getDisplayPath(folderPath))); //throw FileError
+ }
+ catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
+ };
//support fallback instead of an error in this special case
- if (openFileBrowserRequested)
+ if (showInFileBrowserRequested)
{
if (selectionLeft.size() + selectionRight.size() > 1) //do not open more than one Explorer instance!
{
@@ -1535,15 +1563,6 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool
return openExternalApplication(commandLinePhrase, leftSide, {}, { selectionRight[0] });
}
- auto openFolderInFileBrowser = [this](const AbstractPath& folderPath)
- {
- try
- {
- openWithDefaultApplication(utfTo<Zstring>(AFS::getDisplayPath(folderPath))); //throw FileError
- }
- catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
- };
-
if (selectionLeft.empty() && selectionRight.empty())
return openFolderInFileBrowser(leftSide ?
createAbstractPath(firstFolderPair_->getValues().folderPathPhraseLeft) :
@@ -1621,19 +1640,16 @@ void MainDialog::openExternalApplication(const Zstring& commandLinePhrase, bool
setLastOperationLog(r.summary, r.errorLog);
- if (r.summary.resultStatus == SyncResult::aborted)
+ if (r.summary.syncResult == SyncResult::aborted)
return;
//updateGui(); -> not needed
}
//########################################################################################
-
- const Zstring cmdExpanded = expandMacros(commandLinePhrase);
-
try
{
- invokeCommandLine< LEFT_SIDE>(cmdExpanded, selectionLeft, tempFileBuf_); //throw FileError
- invokeCommandLine<RIGHT_SIDE>(cmdExpanded, selectionRight, tempFileBuf_); //
+ invokeCommandLine< LEFT_SIDE>(commandLinePhrase, openWithDefaultAppRequested, selectionLeft, tempFileBuf_); //throw FileError
+ invokeCommandLine<RIGHT_SIDE>(commandLinePhrase, openWithDefaultAppRequested, selectionRight, tempFileBuf_); //
}
catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
}
@@ -3872,7 +3888,7 @@ void MainDialog::OnCompare(wxCommandEvent& event)
setLastOperationLog(r.summary, r.errorLog);
- if (r.summary.resultStatus == SyncResult::aborted)
+ if (r.summary.syncResult == SyncResult::aborted)
return updateGui(); //refresh grid in ANY case! (also on abort)
@@ -3911,15 +3927,15 @@ void MainDialog::OnCompare(wxCommandEvent& event)
fp.setFocus(m_buttonSync);
//mark selected cfg files as "in sync" when there is nothing to do: https://freefilesync.org/forum/viewtopic.php?t=4991
- if (r.summary.resultStatus == SyncResult::finishedSuccess)
+ if (r.summary.syncResult == SyncResult::finishedSuccess)
{
const SyncStatistics st(folderCmp_);
if (st.createCount() +
st.updateCount() +
st.deleteCount() == 0)
{
- flashStatusInformation(_("All files are in sync"));
- updateConfigLastRunStats(std::chrono::system_clock::to_time_t(startTime), r.summary.resultStatus, getNullPath() /*logFilePath*/);
+ flashStatusInformation(_("No files to synchronize"));
+ updateConfigLastRunStats(std::chrono::system_clock::to_time_t(startTime), r.summary.syncResult, getNullPath() /*logFilePath*/);
}
}
}
@@ -4064,11 +4080,10 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
//run this->enableAllElements() BEFORE "exitRequest" buf AFTER StatusHandlerFloatingDialog::reportResults()
//class handling status updates and error messages
- StatusHandlerFloatingDialog statusHandler(this, syncStartTime,
+ StatusHandlerFloatingDialog statusHandler(this, jobNames, syncStartTime,
guiCfg.mainCfg.ignoreErrors,
guiCfg.mainCfg.automaticRetryCount,
guiCfg.mainCfg.automaticRetryDelay,
- jobNames,
globalCfg_.soundFileSyncFinished,
globalCfg_.autoCloseProgressDialog);
try
@@ -4120,7 +4135,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
setLastOperationLog(r.summary, r.errorLog.ptr());
//update last sync stats for the selected cfg files
- updateConfigLastRunStats(std::chrono::system_clock::to_time_t(syncStartTime), r.summary.resultStatus, r.logFilePath);
+ updateConfigLastRunStats(std::chrono::system_clock::to_time_t(syncStartTime), r.summary.syncResult, r.logFilePath);
//remove empty rows: just a beautification, invalid rows shouldn't cause issues
filegrid::getDataView(*m_gridMainC).removeInvalidRows();
@@ -4143,7 +4158,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event)
{
onQueryEndSession(); //(try to) save GlobalSettings.xml => don't block shutdown if failed!!!
shutdownSystem(); //throw FileError
- terminateProcess(0 /*exitCode*/); //no point in continuing and saving cfg again in ~MainDialog()/onQueryEndSession() while the OS will kill us anytime!
+ terminateProcess(FFS_EXIT_SUCCESS); //no point in continuing and saving cfg again in ~MainDialog()/onQueryEndSession() while the OS will kill us anytime!
}
catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
//[!] ignores current error handling setting, BUT this is not a sync error!
@@ -4323,7 +4338,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
{
const wxBitmap syncResultImage = [&]
{
- switch (summary.resultStatus)
+ switch (summary.syncResult)
{
case SyncResult::finishedSuccess:
return getResourceImage(L"result_success");
@@ -4339,7 +4354,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
const wxImage logOverlayImage = [&]
{
- //don't use "resultStatus": There may be errors after sync, e.g. failure to save log file/send email!
+ //don't use "syncResult": There may be errors after sync, e.g. failure to save log file/send email!
if (errorLog)
{
const ErrorLog::Stats logCount = errorLog->getStats();
@@ -4352,7 +4367,7 @@ void MainDialog::setLastOperationLog(const ProcessSummary& summary, const std::s
}();
m_bitmapSyncResult->SetBitmap(syncResultImage);
- m_staticTextSyncResult->SetLabel(getSyncResultLabel(summary.resultStatus));
+ m_staticTextSyncResult->SetLabel(getSyncResultLabel(summary.syncResult));
m_staticTextItemsProcessed->SetLabel(formatNumber(summary.statsProcessed.items));
@@ -4819,7 +4834,7 @@ void MainDialog::setStatusBarFileStats(FileView::FileStats statsLeft,
wxString statusCenterNew;
if (filegrid::getDataView(*m_gridMainC).rowsTotal() > 0)
{
- statusCenterNew = _P("Showing %y of 1 row", "Showing %y of %x rows", filegrid::getDataView(*m_gridMainC).rowsTotal());
+ statusCenterNew = _P("Showing %y of 1 item", "Showing %y of %x items", filegrid::getDataView(*m_gridMainC).rowsTotal());
replace(statusCenterNew, L"%y", formatNumber(filegrid::getDataView(*m_gridMainC).rowsOnView())); //%x used as plural form placeholder!
}
diff --git a/FreeFileSync/Source/ui/progress_indicator.cpp b/FreeFileSync/Source/ui/progress_indicator.cpp
index 7e1ee3d9..2ac9e3c8 100644
--- a/FreeFileSync/Source/ui/progress_indicator.cpp
+++ b/FreeFileSync/Source/ui/progress_indicator.cpp
@@ -8,7 +8,7 @@
#include <memory>
#include <wx/imaglist.h>
#include <wx/wupdlock.h>
-#include <wx/sound.h>
+//#include <wx/sound.h>
#include <wx/app.h>
#include <zen/basic_math.h>
#include <zen/format_unit.h>
@@ -647,14 +647,13 @@ public:
wxFrame* parentFrame,
bool showProgress,
bool autoCloseDialog,
- const std::chrono::system_clock::time_point& syncStartTime,
const std::vector<std::wstring>& jobNames,
- const Zstring& soundFileSyncComplete,
+ const std::chrono::system_clock::time_point& syncStartTime,
bool ignoreErrors,
size_t automaticRetryCount,
PostSyncAction2 postSyncAction);
- Result destroy(bool autoClose, bool restoreParentFrame, SyncResult resultStatus, const SharedRef<const zen::ErrorLog>& log) override;
+ Result destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const SharedRef<const zen::ErrorLog>& log) override;
wxWindow* getWindowIfVisible() override { return this->IsShown() ? this : nullptr; }
//workaround OS X bug: if "this" is used as parent window for a modal dialog then this dialog will erroneously un-hide its parent!
@@ -692,7 +691,7 @@ private:
void OnMinimizeToTray(wxCommandEvent& event) { minimizeToTray(); }
//void OnToggleIgnoreErrors(wxCommandEvent& event) { updateStaticGui(); }
- void showSummary(SyncResult resultStatus, const SharedRef<const ErrorLog>& log);
+ void showSummary(SyncResult syncResult, const SharedRef<const ErrorLog>& log);
void minimizeToTray();
void resumeFromSystray();
@@ -706,7 +705,6 @@ private:
const std::chrono::system_clock::time_point& syncStartTime_;
const wxString jobName_;
- const Zstring soundFileSyncComplete_;
StopWatch stopWatch_;
wxFrame* parentFrame_; //optional
@@ -750,9 +748,8 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF
wxFrame* parentFrame,
bool showProgress,
bool autoCloseDialog,
- const std::chrono::system_clock::time_point& syncStartTime,
const std::vector<std::wstring>& jobNames,
- const Zstring& soundFileSyncComplete,
+ const std::chrono::system_clock::time_point& syncStartTime,
bool ignoreErrors,
size_t automaticRetryCount,
PostSyncAction2 postSyncAction) :
@@ -771,7 +768,6 @@ SyncProgressDialogImpl<TopLevelDialog>::SyncProgressDialogImpl(long style, //wxF
return tmp;
}
()),
-soundFileSyncComplete_(soundFileSyncComplete),
parentFrame_(parentFrame),
userRequestAbort_(userRequestAbort),
syncStat_(&syncStat)
@@ -1266,13 +1262,13 @@ void SyncProgressDialogImpl<TopLevelDialog>::updateStaticGui() //depends on "syn
template <class TopLevelDialog>
-void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultStatus, const SharedRef<const ErrorLog>& log)
+void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult syncResult, const SharedRef<const ErrorLog>& log)
{
assert(syncStat_);
//at the LATEST(!) to prevent access to currentStatusHandler
//enable okay and close events; may be set in this method ONLY
- //In wxWidgets 2.9.3 upwards, the wxWindow::Reparent() below fails on GTK and OS X if window is frozen! http://forums.codeblocks.org/index.php?topic=13388.45
+ //In wxWidgets 2.9.3 upwards, the wxWindow::Reparent() below fails on GTK and OS X if window is frozen! https://forums.codeblocks.org/index.php?topic=13388.45
paused_ = false; //you never know?
@@ -1326,7 +1322,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultStatus
const wxBitmap statusImage = [&]
{
- switch (resultStatus)
+ switch (syncResult)
{
case SyncResult::finishedSuccess:
return getResourceImage(L"result_success");
@@ -1341,12 +1337,12 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultStatus
}();
pnl_.m_bitmapStatus->SetBitmap(statusImage);
- pnl_.m_staticTextPhase->SetLabel(getSyncResultLabel(resultStatus));
+ pnl_.m_staticTextPhase->SetLabel(getSyncResultLabel(syncResult));
//pnl_.m_bitmapStatus->SetToolTip(); -> redundant
//show status on Windows 7 taskbar
if (taskbar_.get())
- switch (resultStatus)
+ switch (syncResult)
{
case SyncResult::finishedSuccess:
case SyncResult::finishedWarning:
@@ -1360,7 +1356,7 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultStatus
}
//----------------------------------
- setExternalStatus(getSyncResultLabel(resultStatus), wxString());
+ setExternalStatus(getSyncResultLabel(syncResult), wxString());
//this->EnableCloseButton(true);
@@ -1440,35 +1436,12 @@ void SyncProgressDialogImpl<TopLevelDialog>::showSummary(SyncResult resultStatus
pnl_.m_panelItemStats->Layout();
pnl_.m_panelTimeStats->Layout();
- //play (optional) sound notification after sync has completed -> only play when waiting on results dialog, seems to be pointless otherwise!
- switch (resultStatus)
- {
- case SyncResult::aborted:
- warn_static("we really should play sound if cancel on error is set, and only not play if user-aborted")
- break;
- case SyncResult::finishedError:
- case SyncResult::finishedWarning:
- case SyncResult::finishedSuccess:
- if (!soundFileSyncComplete_.empty())
- {
- //wxWidgets shows modal error dialog by default => NO!
- wxLog* oldLogTarget = wxLog::SetActiveTarget(new wxLogStderr); //transfer and receive ownership!
- ZEN_ON_SCOPE_EXIT(delete wxLog::SetActiveTarget(oldLogTarget));
-
- wxSound::Play(utfTo<wxString>(soundFileSyncComplete_), wxSOUND_ASYNC);
- }
-
- //if (::GetForegroundWindow() != GetHWND())
- // RequestUserAttention(); -> probably too much since task bar is already colorized with Taskbar::STATUS_ERROR or STATUS_NORMAL
- break;
- }
-
//Raise(); -> don't! user may be watching a movie in the meantime ;) note: resumeFromSystray() also calls Raise()!
}
template <class TopLevelDialog>
-auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restoreParentFrame, SyncResult resultStatus, const SharedRef<const ErrorLog>& log) -> Result
+auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const SharedRef<const ErrorLog>& log) -> Result
{
if (autoClose)
{
@@ -1480,7 +1453,7 @@ auto SyncProgressDialogImpl<TopLevelDialog>::destroy(bool autoClose, bool restor
}
else
{
- showSummary(resultStatus, log);
+ showSummary(syncResult, log);
//wait until user closes the dialog by pressing "okay"
while (!okayPressed_)
@@ -1645,9 +1618,8 @@ SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& user
wxFrame* parentWindow, //may be nullptr
bool showProgress,
bool autoCloseDialog,
- const std::chrono::system_clock::time_point& syncStartTime,
const std::vector<std::wstring>& jobNames,
- const Zstring& soundFileSyncComplete,
+ const std::chrono::system_clock::time_point& syncStartTime,
bool ignoreErrors,
size_t automaticRetryCount,
PostSyncAction2 postSyncAction)
@@ -1657,12 +1629,12 @@ SyncProgressDialog* SyncProgressDialog::create(const std::function<void()>& user
//due to usual "wxBugs", wxDialog on OS X does not float on its parent; wxFrame OTOH does => hack!
//https://groups.google.com/forum/#!topic/wx-users/J5SjjLaBOQE
return new SyncProgressDialogImpl<wxDialog>(wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxMINIMIZE_BOX | wxRESIZE_BORDER, [&](wxDialog& progDlg) { return parentWindow; },
- userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, syncStartTime, jobNames, soundFileSyncComplete, ignoreErrors, automaticRetryCount, postSyncAction);
+ userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, automaticRetryCount, postSyncAction);
}
else //FFS batch job
{
auto dlg = new SyncProgressDialogImpl<wxFrame>(wxDEFAULT_FRAME_STYLE, [](wxFrame& progDlg) { return &progDlg; },
- userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, syncStartTime, jobNames, soundFileSyncComplete, ignoreErrors, automaticRetryCount, postSyncAction);
+ userRequestAbort, syncStat, parentWindow, showProgress, autoCloseDialog, jobNames, syncStartTime, ignoreErrors, automaticRetryCount, postSyncAction);
//only top level windows should have an icon:
dlg->SetIcon(getFfsIcon());
diff --git a/FreeFileSync/Source/ui/progress_indicator.h b/FreeFileSync/Source/ui/progress_indicator.h
index 300078fa..1be7defc 100644
--- a/FreeFileSync/Source/ui/progress_indicator.h
+++ b/FreeFileSync/Source/ui/progress_indicator.h
@@ -62,14 +62,13 @@ struct SyncProgressDialog
wxFrame* parentWindow, //may be nullptr
bool showProgress,
bool autoCloseDialog,
- const std::chrono::system_clock::time_point& syncStartTime,
const std::vector<std::wstring>& jobNames,
- const Zstring& soundFileSyncComplete,
+ const std::chrono::system_clock::time_point& syncStartTime,
bool ignoreErrors,
size_t automaticRetryCount,
PostSyncAction2 postSyncAction);
struct Result { bool autoCloseDialog; };
- virtual Result destroy(bool autoClose, bool restoreParentFrame, SyncResult resultStatus, const zen::SharedRef<const zen::ErrorLog>& log) = 0;
+ virtual Result destroy(bool autoClose, bool restoreParentFrame, SyncResult syncResult, const zen::SharedRef<const zen::ErrorLog>& log) = 0;
//---------------------------------------------------------------------------
virtual wxWindow* getWindowIfVisible() = 0; //may be nullptr; don't abuse, use as parent for modal dialogs only!
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 1e38285c..9ed7713b 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -213,7 +213,7 @@ private:
static bool acceptFileDrop(const std::vector<Zstring>& shellItemPaths);
void onKeyFileDropped(FileDropEvent& event);
- Zstring getFolderPathPhrase() const;
+ AbstractPath getFolderPath() const;
enum class CloudType
{
@@ -305,48 +305,51 @@ CloudSetupDlg::CloudSetupDlg(wxWindow* parent, Zstring& folderPathPhrase, size_t
if (acceptsItemPathPhraseGdrive(folderPathPhrase))
{
type_ = CloudType::gdrive;
- const GdrivePath gdrivePath = getResolvedGooglePath(folderPathPhrase); //noexcept
+ const AbstractPath folderPath = createItemPathGdrive(folderPathPhrase);
+ const Zstring userEmail = extractGdriveEmail(folderPath.afsDevice); //noexcept
- const int selIdx = m_listBoxGdriveUsers->FindString(utfTo<wxString>(gdrivePath.userEmail), false /*caseSensitive*/);
- if (selIdx != wxNOT_FOUND)
+ if (const int selIdx = m_listBoxGdriveUsers->FindString(utfTo<wxString>(userEmail), false /*caseSensitive*/);
+ selIdx != wxNOT_FOUND)
{
m_listBoxGdriveUsers->EnsureVisible(selIdx);
m_listBoxGdriveUsers->SetSelection(selIdx);
}
else
m_listBoxGdriveUsers->DeselectAll();
- m_staticTextGdriveUser->SetLabel (utfTo<wxString>(gdrivePath.userEmail));
- m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + gdrivePath.itemPath.value));
+ m_staticTextGdriveUser->SetLabel (utfTo<wxString>(userEmail));
+ m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + folderPath.afsPath.value));
}
else if (acceptsItemPathPhraseSftp(folderPathPhrase))
{
type_ = CloudType::sftp;
- const SftpPathInfo pi = getResolvedSftpPath(folderPathPhrase); //noexcept
-
- if (pi.login.port > 0)
- m_textCtrlPort->ChangeValue(numberTo<wxString>(pi.login.port));
- m_textCtrlServer ->ChangeValue(utfTo<wxString>(pi.login.server));
- m_textCtrlUserName ->ChangeValue(utfTo<wxString>(pi.login.username));
- sftpAuthType_ = pi.login.authType;
- m_textCtrlPasswordHidden->ChangeValue(utfTo<wxString>(pi.login.password));
- m_textCtrlKeyfilePath ->ChangeValue(utfTo<wxString>(pi.login.privateKeyFilePath));
- m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + pi.afsPath.value));
- m_spinCtrlTimeout ->SetValue(pi.login.timeoutSec);
- m_spinCtrlChannelCountSftp->SetValue(pi.login.traverserChannelsPerConnection);
+ const AbstractPath folderPath = createItemPathSftp(folderPathPhrase);
+ const SftpLoginInfo login = extractSftpLogin(folderPath.afsDevice); //noexcept
+
+ if (login.port > 0)
+ m_textCtrlPort->ChangeValue(numberTo<wxString>(login.port));
+ m_textCtrlServer ->ChangeValue(utfTo<wxString>(login.server));
+ m_textCtrlUserName ->ChangeValue(utfTo<wxString>(login.username));
+ sftpAuthType_ = login.authType;
+ m_textCtrlPasswordHidden->ChangeValue(utfTo<wxString>(login.password));
+ m_textCtrlKeyfilePath ->ChangeValue(utfTo<wxString>(login.privateKeyFilePath));
+ m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + folderPath.afsPath.value));
+ m_spinCtrlTimeout ->SetValue(login.timeoutSec);
+ m_spinCtrlChannelCountSftp->SetValue(login.traverserChannelsPerConnection);
}
else if (acceptsItemPathPhraseFtp(folderPathPhrase))
{
type_ = CloudType::ftp;
- const FtpPathInfo pi = getResolvedFtpPath(folderPathPhrase); //noexcept
-
- if (pi.login.port > 0)
- m_textCtrlPort->ChangeValue(numberTo<wxString>(pi.login.port));
- m_textCtrlServer ->ChangeValue(utfTo<wxString>(pi.login.server));
- m_textCtrlUserName ->ChangeValue(utfTo<wxString>(pi.login.username));
- m_textCtrlPasswordHidden ->ChangeValue(utfTo<wxString>(pi.login.password));
- m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + pi.afsPath.value));
- (pi.login.useTls ? m_radioBtnEncryptSsl : m_radioBtnEncryptNone)->SetValue(true);
- m_spinCtrlTimeout ->SetValue(pi.login.timeoutSec);
+ const AbstractPath folderPath = createItemPathFtp(folderPathPhrase);
+ const FtpLoginInfo login = extractFtpLogin(folderPath.afsDevice); //noexcept
+
+ if (login.port > 0)
+ m_textCtrlPort->ChangeValue(numberTo<wxString>(login.port));
+ m_textCtrlServer ->ChangeValue(utfTo<wxString>(login.server));
+ m_textCtrlUserName ->ChangeValue(utfTo<wxString>(login.username));
+ m_textCtrlPasswordHidden ->ChangeValue(utfTo<wxString>(login.password));
+ m_textCtrlServerPath ->ChangeValue(utfTo<wxString>(FILE_NAME_SEPARATOR + folderPath.afsPath.value));
+ (login.useTls ? m_radioBtnEncryptSsl : m_radioBtnEncryptNone)->SetValue(true);
+ m_spinCtrlTimeout ->SetValue(login.timeoutSec);
}
m_spinCtrlConnectionCount->SetValue(parallelOps);
@@ -452,10 +455,9 @@ void CloudSetupDlg::OnGdriveUserSelect(wxCommandEvent& event)
void CloudSetupDlg::OnDetectServerChannelLimit(wxCommandEvent& event)
{
assert (type_ == CloudType::sftp);
- const SftpPathInfo pi = getResolvedSftpPath(getFolderPathPhrase()); //noexcept
try
{
- const int channelCountMax = getServerMaxChannelsPerConnection(pi.login); //throw FileError
+ const int channelCountMax = getServerMaxChannelsPerConnection(extractSftpLogin(getFolderPath().afsDevice)); //throw FileError
m_spinCtrlChannelCountSftp->SetValue(channelCountMax);
}
catch (const FileError& e)
@@ -586,13 +588,15 @@ void CloudSetupDlg::updateGui()
}
-Zstring CloudSetupDlg::getFolderPathPhrase() const
+AbstractPath CloudSetupDlg::getFolderPath() const
{
+ //clean up (messy) user input, but no trim: support folders with trailing blanks!
+ const AfsPath serverRelPath = sanitizeDeviceRelativePath(utfTo<Zstring>(m_textCtrlServerPath->GetValue()));
+
switch (type_)
{
case CloudType::gdrive:
- return condenseToGoogleFolderPathPhrase(utfTo<Zstring>(m_staticTextGdriveUser->GetLabel()),
- utfTo<Zstring>(m_textCtrlServerPath ->GetValue())); //noexcept
+ return AbstractPath(condenseToGdriveDevice(utfTo<Zstring>(m_staticTextGdriveUser->GetLabel())), serverRelPath); //noexcept
case CloudType::sftp:
{
@@ -605,10 +609,7 @@ Zstring CloudSetupDlg::getFolderPathPhrase() const
login.password = utfTo<Zstring>((m_checkBoxShowPassword->GetValue() ? m_textCtrlPasswordVisible : m_textCtrlPasswordHidden)->GetValue());
login.timeoutSec = m_spinCtrlTimeout->GetValue();
login.traverserChannelsPerConnection = m_spinCtrlChannelCountSftp->GetValue();
-
- auto serverPath = utfTo<Zstring>(m_textCtrlServerPath->GetValue());
- //clean up (messy) user input:
- return condenseToSftpFolderPathPhrase(login, serverPath); //noexcept
+ return AbstractPath(condenseToSftpDevice(login), serverRelPath); //noexcept
}
case CloudType::ftp:
@@ -620,28 +621,26 @@ Zstring CloudSetupDlg::getFolderPathPhrase() const
login.password = utfTo<Zstring>((m_checkBoxShowPassword->GetValue() ? m_textCtrlPasswordVisible : m_textCtrlPasswordHidden)->GetValue());
login.useTls = m_radioBtnEncryptSsl->GetValue();
login.timeoutSec = m_spinCtrlTimeout->GetValue();
-
- auto serverPath = utfTo<Zstring>(m_textCtrlServerPath->GetValue());
- //clean up (messy) user input:
- return condenseToFtpFolderPathPhrase(login, serverPath); //noexcept
+ return AbstractPath(condenseToFtpDevice(login), serverRelPath); //noexcept
}
}
assert(false);
- return Zstr("");
+ return createAbstractPath(Zstr(""));
}
void CloudSetupDlg::OnBrowseCloudFolder(wxCommandEvent& event)
{
- AbstractPath folderPath = createAbstractPath(getFolderPathPhrase()); //noexcept
+ AbstractPath folderPath = getFolderPath(); //noexcept
if (!AFS::getParentPath(folderPath))
try //for (S)FTP it makes more sense to start with the home directory rather than root (which often denies access!)
{
if (type_ == CloudType::sftp)
- folderPath.afsPath = getSftpHomePath(getResolvedSftpPath(getFolderPathPhrase()).login); //throw FileError
+ folderPath.afsPath = getSftpHomePath(extractSftpLogin(folderPath.afsDevice)); //throw FileError
+
if (type_ == CloudType::ftp)
- folderPath.afsPath = getFtpHomePath(getResolvedFtpPath(getFolderPathPhrase()).login); //throw FileError
+ folderPath.afsPath = getFtpHomePath(extractFtpLogin(folderPath.afsDevice)); //throw FileError
}
catch (const FileError& e)
{
@@ -667,7 +666,7 @@ void CloudSetupDlg::OnOkay(wxCommandEvent& event)
}
//-------------------------------------------------------------
- folderPathPhraseOut_ = getFolderPathPhrase();
+ folderPathPhraseOut_ = AFS::getInitPathPhrase(getFolderPath());
parallelOpsOut_ = m_spinCtrlConnectionCount->GetValue();
EndModal(ReturnSmallDlg::BUTTON_OKAY);
@@ -1116,6 +1115,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) :
m_bitmapWarnings ->SetBitmap(shrinkImage(getResourceImage(L"msg_warning").ConvertToImage(), fastFromDIP(20)));
m_bitmapLogFile ->SetBitmap(shrinkImage(getResourceImage(L"log_file" ).ConvertToImage(), fastFromDIP(20)));
m_bitmapNotificationSounds->SetBitmap (getResourceImage(L"notification_sounds"));
+ m_bitmapConsole ->SetBitmap(shrinkImage(getResourceImage(L"command_line").ConvertToImage(), fastFromDIP(20)));
m_bitmapCompareDone ->SetBitmap (getResourceImage(L"compare_sicon"));
m_bitmapSyncDone ->SetBitmap (getResourceImage(L"file_sync_sicon"));
m_bpButtonPlayCompareDone ->SetBitmapLabel(getResourceImage(L"play_sound"));
@@ -1123,7 +1123,7 @@ OptionsDlg::OptionsDlg(wxWindow* parent, XmlGlobalSettings& globalSettings) :
m_bpButtonAddRow ->SetBitmapLabel(getResourceImage(L"item_add"));
m_bpButtonRemoveRow ->SetBitmapLabel(getResourceImage(L"item_remove"));
- m_staticTextAllDialogsShown->SetLabel(L'(' + _("All dialogs shown") + L')');
+ m_staticTextAllDialogsShown->SetLabel(L'(' + _("No dialogs hidden") + L')');
m_staticTextResetDialogs->Wrap(std::max(fastFromDIP(250),
m_buttonRestoreDialogs ->GetSize().x +
@@ -1386,7 +1386,7 @@ void OptionsDlg::OnShowLogFolder(wxHyperlinkEvent& event)
{
try
{
- openWithDefaultApplication(getDefaultLogFolderPath()); //throw FileError
+ openWithDefaultApp(getDefaultLogFolderPath()); //throw FileError
}
catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::error, PopupDialogCfg().setDetailInstructions(e.toString())); }
}
diff --git a/FreeFileSync/Source/ui/status_handler_impl.h b/FreeFileSync/Source/ui/status_handler_impl.h
new file mode 100644
index 00000000..b20ecaf0
--- /dev/null
+++ b/FreeFileSync/Source/ui/status_handler_impl.h
@@ -0,0 +1,69 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#ifndef STATUS_HANDLER_IMPL_H_145234543248059083415565
+#define STATUS_HANDLER_IMPL_H_145234543248059083415565
+
+//#include <vector>
+#include <chrono>
+#include <thread>
+#include <zen/zstring.h>
+//#include <string>
+#include <zen/i18n.h>
+//#include <zen/string_tools.h>
+//#include <zen/basic_math.h>
+//#include "base/process_callback.h"
+//#include "return_codes.h"
+
+
+namespace fff
+{
+namespace
+{
+void delayAndCountDown(const std::wstring& operationName, std::chrono::seconds delay, const std::function<void(const std::wstring& msg)>& notifyStatus)
+{
+ assert(notifyStatus && !zen::endsWith(operationName, L"."));
+
+ const auto delayUntil = std::chrono::steady_clock::now() + delay;
+ for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
+ {
+ const auto timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(delayUntil - now).count();
+ if (notifyStatus)
+ notifyStatus(operationName + L"... " + _P("1 sec", "%x sec", numeric::integerDivideRoundUp(timeMs, 1000)));
+
+ std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2);
+ }
+}
+
+
+void runCommandAndLogErrors(const Zstring& cmdLine, zen::ErrorLog& errorLog)
+{
+ using namespace zen;
+
+ try
+ {
+ //give consoleExecute() some "time to fail", but not too long to hang our process
+ const int DEFAULT_APP_TIMEOUT_MS = 100;
+
+ if (const auto [exitCode, output] = consoleExecute(cmdLine, DEFAULT_APP_TIMEOUT_MS); //throw SysError, SysErrorTimeOut
+ exitCode != 0)
+ throw SysError(formatSystemError("", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
+
+ errorLog.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(cmdLine) + L" [" + replaceCpy(_("Exit code %x"), L"%x", L"0") + L']', MSG_TYPE_INFO);
+ }
+ catch (SysErrorTimeOut&) //child process not failed yet => probably fine :>
+ {
+ errorLog.logMsg(_("Executing command:") + L' ' + utfTo<std::wstring>(cmdLine), MSG_TYPE_INFO);
+ }
+ catch (const SysError& e)
+ {
+ errorLog.logMsg(replaceCpy(_("Command %x failed."), L"%x", fmtPath(cmdLine)) + L"\n\n" + e.toString(), MSG_TYPE_ERROR);
+ }
+}
+}
+}
+
+#endif //STATUS_HANDLER_IMPL_H_145234543248059083415565
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index fe3c00da..b637e0c7 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -436,7 +436,7 @@ showMultipleCfgs_(showMultipleCfgs)
assert(!m_listBoxFolderPair->IsSorted());
- m_listBoxFolderPair->Append(_("Main config"));
+ m_listBoxFolderPair->Append(_("All folder pairs"));
for (const LocalPairConfig& lpc : localPairConfig)
{
std::wstring fpName = getShortDisplayNameForFolderPair(createAbstractPath(lpc.folderPathPhraseLeft ),
diff --git a/FreeFileSync/Source/ui/tray_icon.cpp b/FreeFileSync/Source/ui/tray_icon.cpp
index 883f4235..2e2ada8c 100644
--- a/FreeFileSync/Source/ui/tray_icon.cpp
+++ b/FreeFileSync/Source/ui/tray_icon.cpp
@@ -133,7 +133,7 @@ public:
//Windows User Experience Guidelines: show the context menu rather than doing *nothing* on single left clicks; however:
//MSDN: "Double-clicking the left mouse button actually generates a sequence of four messages: WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, and WM_LBUTTONUP."
- //Reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ms645606
+ //Reference: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondblclk
//=> the only way to distinguish single left click and double-click is to wait wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC) (480ms) which is way too long!
}
diff --git a/FreeFileSync/Source/ui/tree_grid.cpp b/FreeFileSync/Source/ui/tree_grid.cpp
index e9a4ab62..4277111c 100644
--- a/FreeFileSync/Source/ui/tree_grid.cpp
+++ b/FreeFileSync/Source/ui/tree_grid.cpp
@@ -1055,7 +1055,7 @@ private:
{
case WXK_LEFT:
case WXK_NUMPAD_LEFT:
- case WXK_NUMPAD_SUBTRACT: //https://msdn.microsoft.com/en-us/library/ms971323#atg_keyboardshortcuts_windows_shortcut_keys
+ case WXK_NUMPAD_SUBTRACT: //https://docs.microsoft.com/en-us/previous-versions/windows/desktop/dnacc/guidelines-for-keyboard-user-interface-design#atg_keyboardshortcuts_windows_shortcut_keys
switch (treeDataView_.getStatus(row))
{
case TreeView::STATUS_EXPANDED:
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index d101d97a..e45bf525 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "10.22"; //internal linkage!
+const char ffsVersion[] = "10.23"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/libcurl/curl_wrap.h b/libcurl/curl_wrap.h
index 40694e71..40a330ef 100644
--- a/libcurl/curl_wrap.h
+++ b/libcurl/curl_wrap.h
@@ -151,7 +151,7 @@ void applyCurlOptions(CURL* easyHandle, const std::vector<CurlOption>& options)
{
const CURLcode rc = ::curl_easy_setopt(easyHandle, opt.option, opt.value);
if (rc != CURLE_OK)
- throw SysError(formatSystemError(L"curl_easy_setopt " + numberTo<std::wstring>(static_cast<int>(opt.option)),
+ throw SysError(formatSystemError("curl_easy_setopt(" + numberTo<std::string>(static_cast<int>(opt.option)) + ")",
formatCurlStatusCode(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
}
}
diff --git a/libcurl/rest.cpp b/libcurl/rest.cpp
index 0d14dfc2..9b609935 100644
--- a/libcurl/rest.cpp
+++ b/libcurl/rest.cpp
@@ -33,7 +33,7 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
{
easyHandle_ = ::curl_easy_init();
if (!easyHandle_)
- throw SysError(formatSystemError(L"curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), std::wstring()));
+ throw SysError(formatSystemError("curl_easy_init", formatCurlStatusCode(CURLE_OUT_OF_MEMORY), L""));
}
else
::curl_easy_reset(easyHandle_);
@@ -168,7 +168,7 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
std::wstring errorMsg = trimCpy(utfTo<std::wstring>(curlErrorBuf)); //optional
if (httpStatus != 0) //optional
- errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpStatus(httpStatus);
+ errorMsg += (errorMsg.empty() ? L"" : L"\n") + formatHttpError(httpStatus);
#if 0
//utfTo<std::wstring>(::curl_easy_strerror(ec)) is uninteresting
//use CURLINFO_OS_ERRNO ?? https://curl.haxx.se/libcurl/c/CURLINFO_OS_ERRNO.html
@@ -177,7 +177,7 @@ HttpSession::Result HttpSession::perform(const std::string& serverRelPath,
if (nativeErrorCode != 0)
errorMsg += (errorMsg.empty() ? L"" : L"\n") + std::wstring(L"Native error code: ") + numberTo<std::wstring>(nativeErrorCode);
#endif
- throw SysError(formatSystemError(L"curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
+ throw SysError(formatSystemError("curl_easy_perform", formatCurlStatusCode(rcPerf), errorMsg));
}
lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
diff --git a/xBRZ/src/xbrz.cpp b/xBRZ/src/xbrz.cpp
index e2c25810..81153375 100644
--- a/xBRZ/src/xbrz.cpp
+++ b/xBRZ/src/xbrz.cpp
@@ -7,6 +7,7 @@
// * to link the code of this program with the following libraries *
// * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
+// * *
// * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
// * If you modify this file, you may extend this exception to your version *
@@ -15,10 +16,10 @@
// ****************************************************************************
#include "xbrz.h"
-#include <cassert>
-#include <vector>
#include <algorithm>
+#include <cassert>
#include <cmath> //std::sqrt
+#include <vector>
#include "xbrz_tools.h"
using namespace xbrz;
@@ -1256,8 +1257,8 @@ void bilinearScaleCpu(const uint32_t* src, int srcWidth, int srcHeight,
void bilinearScaleAmp(const uint32_t* src, int srcWidth, int srcHeight, //throw concurrency::runtime_exception
/**/ uint32_t* trg, int trgWidth, int trgHeight)
{
- //C++ AMP reference: https://msdn.microsoft.com/en-us/library/hh289390.aspx
- //introduction to C++ AMP: https://msdn.microsoft.com/en-us/magazine/hh882446.aspx
+ //C++ AMP reference: https://docs.microsoft.com/en-us/cpp/parallel/amp/reference/reference-cpp-amp
+ //introduction to C++ AMP: https://docs.microsoft.com/en-us/archive/msdn-magazine/2012/april/c-a-code-based-introduction-to-c-amp
using namespace concurrency;
//TODO: pitch
@@ -1276,7 +1277,7 @@ void bilinearScaleAmp(const uint32_t* src, int srcWidth, int srcHeight, //throw
const int x = idx[1];
//Perf notes:
// -> float-based calculation is (almost) 2x as fas as double!
- // -> no noticeable improvement via tiling: https://msdn.microsoft.com/en-us/magazine/hh882447.aspx
+ // -> no noticeable improvement via tiling: https://docs.microsoft.com/en-us/archive/msdn-magazine/2012/april/c-amp-introduction-to-tiling-in-c-amp
// -> no noticeable improvement with restrict(amp,cpu)
// -> iterating over y-axis only is significantly slower!
// -> pre-calculating x,y-dependent variables in a buffer + array_view<> is ~ 20 % slower!
diff --git a/xBRZ/src/xbrz.h b/xBRZ/src/xbrz.h
index 492fb43a..c0778cf1 100644
--- a/xBRZ/src/xbrz.h
+++ b/xBRZ/src/xbrz.h
@@ -7,6 +7,7 @@
// * to link the code of this program with the following libraries *
// * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
+// * *
// * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
// * If you modify this file, you may extend this exception to your version *
diff --git a/xBRZ/src/xbrz_config.h b/xBRZ/src/xbrz_config.h
index fcfda99a..84f82dcf 100644
--- a/xBRZ/src/xbrz_config.h
+++ b/xBRZ/src/xbrz_config.h
@@ -7,6 +7,7 @@
// * to link the code of this program with the following libraries *
// * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
+// * *
// * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
// * If you modify this file, you may extend this exception to your version *
diff --git a/xBRZ/src/xbrz_tools.h b/xBRZ/src/xbrz_tools.h
index b8bb8aa0..cd6acc63 100644
--- a/xBRZ/src/xbrz_tools.h
+++ b/xBRZ/src/xbrz_tools.h
@@ -7,6 +7,7 @@
// * to link the code of this program with the following libraries *
// * (or with modified versions that use the same licenses), and distribute *
// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe *
+// * *
// * You must obey the GNU General Public License in all respects for all of *
// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. *
// * If you modify this file, you may extend this exception to your version *
diff --git a/zen/basic_math.h b/zen/basic_math.h
index 26dda9a6..0a226555 100644
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -271,7 +271,7 @@ template <class InputIterator> inline
double stdDeviation(InputIterator first, InputIterator last, double* arithMean)
{
//implementation minimizing rounding errors, see: https://en.wikipedia.org/wiki/Standard_deviation
- //combined with technique avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation
+ //combined with technique avoiding overflow, see: https://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation
size_t n = 0;
double mean = 0;
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 307b48e5..d02e229e 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -52,7 +52,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
//init
pimpl_->notifDescr = ::inotify_init();
if (pimpl_->notifDescr == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"inotify_init");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "inotify_init");
ZEN_ON_SCOPE_FAIL( ::close(pimpl_->notifDescr); );
@@ -61,10 +61,10 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
{
int flags = ::fcntl(pimpl_->notifDescr, F_GETFL);
if (flags != -1)
- initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
+ initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) == 0;
}
if (!initSuccess)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"fcntl");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl");
//add watches
for (const Zstring& subDirPath : fullFolderList)
@@ -85,10 +85,10 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
const ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
if (ec == ENOSPC) //fix misleading system message "No space left on device"
throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)),
- formatSystemError(L"inotify_add_watch", L"ENOSPC",
+ formatSystemError("inotify_add_watch", L"ENOSPC",
L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec));
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError("inotify_add_watch", ec));
}
pimpl_->watchedPaths.emplace(wd, subDirPath);
@@ -102,7 +102,7 @@ DirWatcher::~DirWatcher()
}
-std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval) //throw FileError
+std::vector<DirWatcher::Change> DirWatcher::fetchChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval) //throw FileError
{
std::vector<std::byte> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1));
@@ -117,12 +117,12 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
if (bytesRead < 0)
{
if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
- return std::vector<Entry>();
+ return std::vector<Change>();
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"read");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "read");
}
- std::vector<Entry> output;
+ std::vector<Change> output;
ssize_t bytePos = 0;
while (bytePos < bytesRead)
@@ -140,15 +140,15 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
if ((evt.mask & IN_CREATE) ||
(evt.mask & IN_MOVED_TO))
- output.push_back({ ACTION_CREATE, itemPath });
+ output.push_back({ ChangeType::create, itemPath });
else if ((evt.mask & IN_MODIFY) ||
(evt.mask & IN_CLOSE_WRITE))
- output.push_back({ ACTION_UPDATE, itemPath });
+ output.push_back({ ChangeType::update, itemPath });
else if ((evt.mask & IN_DELETE ) ||
(evt.mask & IN_DELETE_SELF) ||
(evt.mask & IN_MOVE_SELF ) ||
(evt.mask & IN_MOVED_FROM))
- output.push_back({ ACTION_DELETE, itemPath });
+ output.push_back({ ChangeType::remove, itemPath });
}
}
bytePos += sizeof(struct ::inotify_event) + evt.len;
diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h
index 4d514e89..3103039d 100644
--- a/zen/dir_watcher.h
+++ b/zen/dir_watcher.h
@@ -16,23 +16,23 @@
namespace zen
{
-//Windows: ReadDirectoryChangesW https://msdn.microsoft.com/en-us/library/aa365465
+//Windows: ReadDirectoryChangesW https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
//Linux: inotify https://linux.die.net/man/7/inotify
-//OS X: kqueue https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
+//macOS: kqueue https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
//watch directory including subdirectories
/*
!Note handling of directories!:
Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal,
(watchting for GUID_DEVINTERFACE_WPD OTOH works fine!)
- however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped!
+ however manual unmount IS notified (e.g. USB stick removal, then re-insert), but watching is stopped!
Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders
now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!)
Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher!
- removal of top watched directory is NOT notified!
+ removal of base directory is NOT notified!
- OS X: everything works as expected; renaming of top level folder is also detected
+ macOS: everything works as expected; renaming of base directory is also detected
Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected
*/
@@ -42,21 +42,22 @@ public:
DirWatcher(const Zstring& dirPath); //throw FileError
~DirWatcher();
- enum ActionType
+ enum class ChangeType
{
- ACTION_CREATE, //informal!
- ACTION_UPDATE, //use for debugging/logging only!
- ACTION_DELETE, //
+ create, //informal!
+ update, //use for debugging/logging only!
+ remove, //
+ baseFolderUnavailable, //1. not existing or 2. can't access
};
- struct Entry
+ struct Change
{
- ActionType action = ACTION_CREATE;
+ ChangeType type = ChangeType::create;
Zstring itemPath;
};
//extract accumulated changes since last call
- std::vector<Entry> getChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval); //throw FileError
+ std::vector<Change> fetchChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval); //throw FileError
private:
DirWatcher (const DirWatcher&) = delete;
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 4f6704d2..cb5a45ed 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -98,7 +98,7 @@ ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
{
struct ::stat itemInfo = {};
if (::lstat(itemPath.c_str(), &itemInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat");
if (S_ISLNK(itemInfo.st_mode))
return ItemType::SYMLINK;
@@ -174,7 +174,7 @@ uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0
{
struct ::statfs info = {};
if (::statfs(path.c_str(), &info) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), "statfs");
return static_cast<uint64_t>(info.f_bsize) * info.f_bavail;
}
@@ -184,7 +184,7 @@ VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
{
struct ::stat fileInfo = {};
if (::stat(itemPath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "stat");
return fileInfo.st_dev;
}
@@ -194,7 +194,7 @@ uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
{
struct ::stat fileInfo = {};
if (::stat(filePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat");
return fileInfo.st_size;
}
@@ -211,7 +211,7 @@ Zstring zen::getTempFolderPath() //throw FileError
void zen::removeFilePlain(const Zstring& filePath) //throw FileError
{
- const wchar_t functionName[] = L"unlink";
+ const char* functionName = "unlink";
if (::unlink(filePath.c_str()) != 0)
{
ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
@@ -231,7 +231,7 @@ void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
{
- const wchar_t functionName[] = L"rmdir";
+ const char* functionName = "rmdir";
if (::rmdir(dirPath.c_str()) != 0)
{
ErrorCode ec = getLastError(); //copy before making other system calls!
@@ -241,7 +241,7 @@ void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
if (symlinkExists)
{
if (::unlink(dirPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), "unlink");
return;
}
throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
@@ -312,7 +312,7 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r
auto throwException = [&](int ec)
{
const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo));
- const std::wstring errorDescr = formatSystemError(L"rename", ec);
+ const std::wstring errorDescr = formatSystemError("rename", ec);
if (ec == EXDEV)
throw ErrorMoveUnsupported(errorMsg, errorDescr);
@@ -329,7 +329,7 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r
{
struct ::stat infoSrc = {};
if (::lstat(pathFrom.c_str(), &infoSrc) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), "stat");
struct ::stat infoTrg = {};
if (::lstat(pathTo.c_str(), &infoTrg) == 0)
@@ -394,16 +394,16 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim
//in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387
const int fdFile = ::open(itemPath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP!
if (fdFile == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "open");
ZEN_ON_SCOPE_EXIT(::close(fdFile));
if (::futimens(fdFile, newTimes) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "futimens");
}
else
{
if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "utimensat");
}
}
@@ -442,7 +442,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
return;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), "getfilecon");
}
ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
@@ -470,7 +470,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
::setfilecon(target.c_str(), contextSource) :
::lsetfilecon(target.c_str(), contextSource);
if (rv3 < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), "setfilecon");
}
#endif
}
@@ -488,26 +488,26 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa
if (procSl == ProcSymlink::FOLLOW)
{
if (::stat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), "stat");
if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chown");
if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chmod");
}
else
{
if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), "lstat");
if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "lchown");
const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chmod");
}
}
@@ -515,19 +515,25 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa
void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTargetExisting
{
+ auto getErrorMsg = [&] { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)); };
+
+ //deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
+ if (endsWith(dirPath, Zstr(' ')) ||
+ endsWith(dirPath, Zstr('.')))
+ throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid trailing character \"%x\".", L"%x", utfTo<std::wstring>(dirPath.end()[-1])));
+
const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
if (::mkdir(dirPath.c_str(), mode) != 0)
{
const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath));
- const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
+ const std::wstring errorDescr = formatSystemError("mkdir", lastError);
if (lastError == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
+ throw ErrorTargetExisting(getErrorMsg(), errorDescr);
//else if (lastError == ENOENT)
// throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
+ throw FileError(getErrorMsg(), errorDescr);
}
}
@@ -575,7 +581,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool
const Zstring linkPath = getSymlinkTargetRaw(sourcePath); //throw FileError; accept broken symlinks
if (::symlink(linkPath.c_str(), targetPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L'\n' + fmtPath(sourcePath)), L"%y", L'\n' + fmtPath(targetPath)), L"symlink");
+ THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L'\n' + fmtPath(sourcePath)), L"%y", L'\n' + fmtPath(targetPath)), "symlink");
//allow only consistent objects to be created -> don't place before ::symlink(); targetPath may already exist!
ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetPath); /*throw FileError*/ }
@@ -584,7 +590,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool
//file times: essential for syncing a symlink: enforce this! (don't just try!)
struct ::stat sourceInfo = {};
if (::lstat(sourcePath.c_str(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourcePath)), "lstat");
setWriteTimeNative(targetPath, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
@@ -605,7 +611,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
struct ::stat sourceInfo = {};
if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), "fstat");
const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
//it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
@@ -616,7 +622,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
{
const int ec = errno; //copy before making other system calls!
const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
+ const std::wstring errorDescr = formatSystemError("open", ec);
if (ec == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
@@ -639,7 +645,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
struct ::stat targetInfo = {};
if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), "fstat");
//close output file handle before setting file time; also good place to catch errors when closing stream!
fileOut.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed
@@ -649,7 +655,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
{
//we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
//this triggers bugs on samba shares where the modification time is set to current time instead.
- //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
+ //Linux: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
// http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
//OS X: https://freefilesync.org/forum/viewtopic.php?t=356
setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index b78259e0..942f367f 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -37,7 +37,7 @@ void FileBase::close() //throw FileError
//no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
if (::close(fileHandle_) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "close");
}
//----------------------------------------------------------------------------------------------------
@@ -73,10 +73,10 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro
}
//else: let ::open() fail for errors like "not existing"
- //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html
+ //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html
const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC);
if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), "open");
return fileHandle; //pass ownership
}
}
@@ -92,7 +92,7 @@ FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbuffered
{
//optimize read-ahead on input file:
if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise");
}
@@ -113,9 +113,9 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, E
//read() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/read.2.html
if (bytesRead < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), "read");
if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("ReadFile", L"", L"Buffer overflow."));
//if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"
@@ -180,7 +180,7 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::Acc
{
const int ec = errno; //copy before making other system calls!
const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
+ const std::wstring errorDescr = formatSystemError("open", ec);
if (ec == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
@@ -231,10 +231,10 @@ size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw Fil
if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
errno = ENOSPC;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "write");
}
if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("write", L"", L"Buffer overflow."));
//if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
return bytesWritten;
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index 2d2a0cce..48185516 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -26,7 +26,7 @@ void zen::traverseFolder(const Zstring& dirPath,
{
DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
if (!folder)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), "opendir");
ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash
for (;;)
@@ -38,7 +38,7 @@ void zen::traverseFolder(const Zstring& dirPath,
if (errno == 0) //errno left unchanged => no more items
return;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), "readdir");
//don't retry but restart dir traversal on error! https://devblogs.microsoft.com/oldnewthing/20140612-00/?p=753/
}
@@ -51,7 +51,7 @@ void zen::traverseFolder(const Zstring& dirPath,
const Zstring& itemName = itemNameRaw;
if (itemName.empty()) //checks result of normalizeUtfForPosix, too!
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name.");
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("readdir", L"", L"Data corruption; item with empty name."));
const Zstring& itemPath = appendSeparator(dirPath) + itemName;
@@ -59,7 +59,7 @@ void zen::traverseFolder(const Zstring& dirPath,
try
{
if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat");
}
catch (const FileError& e)
{
diff --git a/zen/guid.h b/zen/guid.h
index 88059be8..f4af4880 100644
--- a/zen/guid.h
+++ b/zen/guid.h
@@ -27,7 +27,7 @@ std::string generateGUID() //creates a 16-byte GUID
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25) //getentropy() requires glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17
if (::getentropy(&guid[0], guid.size()) != 0) //"The maximum permitted value for the length argument is 256"
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"getentropy", errno)));
+ utfTo<std::string>(formatSystemError("getentropy", errno)));
#else
class RandomGeneratorPosix
{
@@ -36,7 +36,7 @@ std::string generateGUID() //creates a 16-byte GUID
{
if (fd_ == -1)
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"open", errno)));
+ utfTo<std::string>(formatSystemError("open", errno)));
}
~RandomGeneratorPosix() { ::close(fd_); }
@@ -48,7 +48,7 @@ std::string generateGUID() //creates a 16-byte GUID
const ssize_t bytesRead = ::read(fd_, static_cast<char*>(buf) + offset, size - offset);
if (bytesRead < 1) //0 means EOF => error in this context (should check for buffer overflow, too?)
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"read", bytesRead < 0 ? errno : EIO)));
+ utfTo<std::string>(formatSystemError("read", bytesRead < 0 ? errno : EIO)));
offset += bytesRead;
assert(offset <= size);
}
diff --git a/zen/http.cpp b/zen/http.cpp
index c6a390de..848b2cb3 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -59,7 +59,7 @@ public:
else //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services
socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError
- //we don't support "chunked and gzip transfer encoding" => HTTP 1.0
+ //we don't support "chunked and gzip transfer encoding" => HTTP 1.0 => no "Content-Length" support!
headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0
headers["User-Agent"] = utfTo<std::string>(userAgent);
headers["Accept" ] = "*/*"; //won't hurt?
@@ -194,7 +194,8 @@ private:
contentRemaining_ -= bytesReceived;
if (bytesReceived == 0 && contentRemaining_ > 0)
- throw SysError(replaceCpy<std::wstring>(L"HttpInputStream::tryRead: incomplete server response; %x more bytes expected.", L"%x", numberTo<std::wstring>(contentRemaining_)));
+ throw SysError(formatSystemError("HttpInputStream::tryRead", L"", L"Incomplete server response: " +
+ numberTo<std::wstring>(contentRemaining_) + L" more bytes expected."));
return bytesReceived; //"zero indicates end of file"
}
@@ -259,8 +260,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
}
else
{
- if (httpStatus != 200) //HTTP_STATUS_OK(200)
- throw SysError(formatHttpStatus(httpStatus)); //e.g. HTTP_STATUS_NOT_FOUND(404)
+ if (httpStatus != 200) //HTTP_STATUS_OK
+ throw SysError(formatHttpError(httpStatus)); //e.g. "HTTP status 404: Not found."
return response;
}
@@ -380,9 +381,9 @@ bool zen::internetIsAlive() //noexcept
}
-std::wstring zen::formatHttpStatus(int sc)
+std::wstring zen::formatHttpError(int sc)
{
- const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ const wchar_t* statusDescr = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
{
switch (sc)
{
@@ -455,10 +456,7 @@ std::wstring zen::formatHttpStatus(int sc)
}
}();
- if (strLength(statusText) == 0)
- return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x.", L"%x", numberTo<std::wstring>(sc)));
- else
- return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x: ", L"%x", numberTo<std::wstring>(sc)) + statusText);
+ return formatSystemError("", L"HTTP status " + numberTo<std::wstring>(sc), statusDescr);
}
diff --git a/zen/http.h b/zen/http.h
index 09395f8f..6cb107bf 100644
--- a/zen/http.h
+++ b/zen/http.h
@@ -54,7 +54,7 @@ HttpInputStream sendHttpPost(const Zstring& url,
const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X
bool internetIsAlive(); //noexcept
-std::wstring formatHttpStatus(int httpStatus);
+std::wstring formatHttpError(int httpStatus);
bool isValidEmail(const std::string& email);
std::string htmlSpecialChars(const std::string_view& str);
diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp
index 0f1da3fc..1feeb1a9 100644
--- a/zen/open_ssl.cpp
+++ b/zen/open_ssl.cpp
@@ -18,7 +18,7 @@ using namespace zen;
#error FFS, we are royally screwed!
#endif
-static_assert(OPENSSL_VERSION_NUMBER >= 0x1010105fL, "OpenSSL version too old");
+static_assert(OPENSSL_VERSION_NUMBER >= 0x10100000L, "OpenSSL version too old");
void zen::openSslInit()
@@ -57,16 +57,16 @@ thread_local OpenSslThreadCleanUp tearDownOpenSslThreadData;
openssl dgst -sha256 -verify public.pem -signature file.sig file.txt */
-std::wstring formatOpenSSLError(const std::wstring& functionName, unsigned long ec)
+std::wstring formatOpenSSLError(const char* functionName, unsigned long ec)
{
char errorBuf[256] = {}; //== buffer size used by ERR_error_string(); err.c: it seems the message uses at most ~200 bytes
::ERR_error_string_n(ec, errorBuf, sizeof(errorBuf)); //includes null-termination
- return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec)), utfTo<std::wstring>(errorBuf));
+ return formatSystemError(functionName, replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(ec)), utfTo<std::wstring>(errorBuf));
}
-std::wstring formatLastOpenSSLError(const std::wstring& functionName)
+std::wstring formatLastOpenSSLError(const char* functionName)
{
const auto ec = ::ERR_peek_last_error();
::ERR_clear_error(); //clean up for next OpenSSL operation on this thread
@@ -80,19 +80,19 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError
EVP_PKEY_CTX* keyCtx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, //int id,
nullptr); //ENGINE* e
if (!keyCtx)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_new_id"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_id"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(keyCtx));
if (::EVP_PKEY_keygen_init(keyCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen_init"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_keygen_init"));
//"RSA keys set the key length during key generation rather than parameter generation"
if (::EVP_PKEY_CTX_set_rsa_keygen_bits(keyCtx, bits) <= 0) //"[...] return a positive value for success" => effectively returns "1"
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_set_rsa_keygen_bits"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_set_rsa_keygen_bits"));
EVP_PKEY* keyPair = nullptr;
if (::EVP_PKEY_keygen(keyCtx, &keyPair) != 1)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_keygen"));
return std::shared_ptr<EVP_PKEY>(keyPair, ::EVP_PKEY_free);
}
@@ -101,11 +101,11 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError
using BioToEvpFunc = EVP_PKEY* (*)(BIO* bp, EVP_PKEY** x, pem_password_cb* cb, void* u);
-std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpFunc bioToEvp, const wchar_t* functionName) //throw SysError
+std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpFunc bioToEvp, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size()));
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf"));
+ throw SysError(formatLastOpenSSLError("BIO_new_mem_buf"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp,
@@ -119,11 +119,11 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpF
using BioToRsaFunc = RSA* (*)(BIO* bp, RSA** x, pem_password_cb* cb, void* u);
-std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaFunc bioToRsa, const wchar_t* functionName) //throw SysError
+std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaFunc bioToRsa, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size()));
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf"));
+ throw SysError(formatLastOpenSSLError("BIO_new_mem_buf"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
RSA* rsa = bioToRsa(bio, //BIO* bp,
@@ -136,11 +136,11 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaF
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free);
if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA"));
return sharedKey;
}
@@ -153,13 +153,13 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
{
case RsaStreamType::pkix:
return publicKey ?
- streamToEvpKey(keyStream, ::PEM_read_bio_PUBKEY, L"PEM_read_bio_PUBKEY") : //throw SysError
- streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, L"PEM_read_bio_PrivateKey"); //
+ streamToEvpKey(keyStream, ::PEM_read_bio_PUBKEY, "PEM_read_bio_PUBKEY") : //throw SysError
+ streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, "PEM_read_bio_PrivateKey"); //
case RsaStreamType::pkcs1:
return publicKey ?
- streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, L"PEM_read_bio_RSAPublicKey") : //throw SysError
- streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, L"PEM_read_bio_RSAPrivateKey"); //
+ streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, "PEM_read_bio_RSAPublicKey") : //throw SysError
+ streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, "PEM_read_bio_RSAPrivateKey"); //
case RsaStreamType::raw:
break;
@@ -171,7 +171,7 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
&tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp,
static_cast<long>(keyStream.size())); //long length
if (!evp)
- throw SysError(formatLastOpenSSLError(publicKey ? L"d2i_PublicKey" : L"d2i_PrivateKey"));
+ throw SysError(formatLastOpenSSLError(publicKey ? "d2i_PublicKey" : "d2i_PrivateKey"));
return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free);
}
@@ -179,11 +179,11 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
using EvpToBioFunc = int (*)(BIO* bio, EVP_PKEY* evp);
-std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const wchar_t* functionName) //throw SysError
+std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new(BIO_s_mem());
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new"));
+ throw SysError(formatLastOpenSSLError("BIO_new"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
if (evpToBio(bio, evp) != 1)
@@ -191,44 +191,44 @@ std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const wchar_t*
//---------------------------------------------
const int keyLen = BIO_pending(bio);
if (keyLen < 0)
- throw SysError(formatLastOpenSSLError(L"BIO_pending"));
+ throw SysError(formatLastOpenSSLError("BIO_pending"));
if (keyLen == 0)
- throw SysError(L"BIO_pending failed."); //no more error details
+ throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details
std::string keyStream(keyLen, '\0');
if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen)
- throw SysError(formatLastOpenSSLError(L"BIO_read"));
+ throw SysError(formatLastOpenSSLError("BIO_read"));
return keyStream;
}
using RsaToBioFunc = int (*)(BIO* bp, RSA* x);
-std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const wchar_t* functionName) //throw SysError
+std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new(BIO_s_mem());
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new"));
+ throw SysError(formatLastOpenSSLError("BIO_new"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference!
if (!rsa)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_get0_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_get0_RSA"));
if (rsaToBio(bio, rsa) != 1)
throw SysError(formatLastOpenSSLError(functionName));
//---------------------------------------------
const int keyLen = BIO_pending(bio);
if (keyLen < 0)
- throw SysError(formatLastOpenSSLError(L"BIO_pending"));
+ throw SysError(formatLastOpenSSLError("BIO_pending"));
if (keyLen == 0)
- throw SysError(L"BIO_pending failed."); //no more error details
+ throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details
std::string keyStream(keyLen, '\0');
if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen)
- throw SysError(formatLastOpenSSLError(L"BIO_read"));
+ throw SysError(formatLastOpenSSLError("BIO_read"));
return keyStream;
}
@@ -266,13 +266,13 @@ std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey)
{
case RsaStreamType::pkix:
return publicKey ?
- evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, L"PEM_write_bio_PUBKEY") : //throw SysError
- evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, L"PEM_write_bio_PrivateKey"); //
+ evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, "PEM_write_bio_PUBKEY") : //throw SysError
+ evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, "PEM_write_bio_PrivateKey"); //
case RsaStreamType::pkcs1:
return publicKey ?
- evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, L"PEM_write_bio_RSAPublicKey") : //throw SysError
- evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, L"PEM_write_bio_RSAPrivateKey"); //
+ evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, "PEM_write_bio_RSAPublicKey") : //throw SysError
+ evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, "PEM_write_bio_RSAPrivateKey"); //
case RsaStreamType::raw:
break;
@@ -281,7 +281,7 @@ std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey)
unsigned char* buf = nullptr;
const int bufSize = (publicKey ? ::i2d_PublicKey : ::i2d_PrivateKey)(evp, &buf);
if (bufSize <= 0)
- throw SysError(formatLastOpenSSLError(publicKey ? L"i2d_PublicKey" : L"i2d_PrivateKey"));
+ throw SysError(formatLastOpenSSLError(publicKey ? "i2d_PublicKey" : "i2d_PrivateKey"));
ZEN_ON_SCOPE_EXIT(::OPENSSL_free(buf)); //memory is only allocated for bufSize > 0
return { reinterpret_cast<const char*>(buf), static_cast<size_t>(bufSize) };
@@ -294,7 +294,7 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
//https://www.openssl.org/docs/manmaster/man3/EVP_DigestSign.html
EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create();
if (!mdctx)
- throw SysError(L"EVP_MD_CTX_create failed."); //no more error details
+ throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx));
if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx,
@@ -302,18 +302,18 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
EVP_sha256(), //const EVP_MD* type,
nullptr, //ENGINE* e,
privateKey) != 1) //EVP_PKEY* pkey
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignInit"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignInit"));
if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx,
message.c_str(), //const void* d,
message.size()) != 1) //size_t cnt
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignUpdate"));
size_t sigLenMax = 0; //"first call to EVP_DigestSignFinal returns the maximum buffer size required"
if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx,
nullptr, //unsigned char* sigret,
&sigLenMax) != 1) //size_t* siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal"));
std::string signature(sigLenMax, '\0');
size_t sigLen = sigLenMax;
@@ -321,7 +321,7 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx,
reinterpret_cast<unsigned char*>(&signature[0]), //unsigned char* sigret,
&sigLen) != 1) //size_t* siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal"));
signature.resize(sigLen);
return signature;
@@ -333,7 +333,7 @@ void verifySignature(const std::string& message, const std::string& signature, E
//https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerify.html
EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create();
if (!mdctx)
- throw SysError(L"EVP_MD_CTX_create failed."); //no more error details
+ throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx));
if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx,
@@ -341,17 +341,17 @@ void verifySignature(const std::string& message, const std::string& signature, E
EVP_sha256(), //const EVP_MD* type,
nullptr, //ENGINE* e,
publicKey) != 1) //EVP_PKEY* pkey
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyInit"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyInit"));
if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx,
message.c_str(), //const void* d,
message.size()) != 1) //size_t cnt
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyUpdate"));
if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx,
reinterpret_cast<const unsigned char*>(signature.c_str()), //const unsigned char* sig,
signature.size()) != 1) //size_t siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyFinal"));
}
}
@@ -494,31 +494,31 @@ public:
ctx_ = ::SSL_CTX_new(::TLS_client_method());
if (!ctx_)
- throw SysError(formatLastOpenSSLError(L"SSL_CTX_new"));
+ throw SysError(formatLastOpenSSLError("SSL_CTX_new"));
ssl_ = ::SSL_new(ctx_);
if (!ssl_)
- throw SysError(formatLastOpenSSLError(L"SSL_new"));
+ throw SysError(formatLastOpenSSLError("SSL_new"));
BIO* bio = ::BIO_new_socket(socket, BIO_NOCLOSE);
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_socket"));
+ throw SysError(formatLastOpenSSLError("BIO_new_socket"));
::SSL_set0_rbio(ssl_, bio); //pass ownership
if (::BIO_up_ref(bio) != 1)
- throw SysError(formatLastOpenSSLError(L"BIO_up_ref"));
+ throw SysError(formatLastOpenSSLError("BIO_up_ref"));
::SSL_set0_wbio(ssl_, bio); //pass ownership
assert(::SSL_get_mode(ssl_) == SSL_MODE_AUTO_RETRY); //verify OpenSSL default
::SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
if (::SSL_set_tlsext_host_name(ssl_, server.c_str()) != 1) //enable SNI (Server Name Indication)
- throw SysError(formatLastOpenSSLError(L"SSL_set_tlsext_host_name"));
+ throw SysError(formatLastOpenSSLError("SSL_set_tlsext_host_name"));
if (caCertFilePath)
{
if (!::SSL_CTX_load_verify_locations(ctx_, utfTo<std::string>(*caCertFilePath).c_str(), nullptr))
- throw SysError(formatLastOpenSSLError(L"SSL_CTX_load_verify_locations"));
+ throw SysError(formatLastOpenSSLError("SSL_CTX_load_verify_locations"));
//alternative: SSL_CTX_set_default_verify_paths(): use OpenSSL default paths considering SSL_CERT_FILE environment variable
//1. enable check for valid certificate: see SSL_get_verify_result()
@@ -526,18 +526,18 @@ public:
//2. enable check that the certificate matches our host: see SSL_get_verify_result()
if (::SSL_set1_host(ssl_, server.c_str()) != 1) //no ownership transfer
- throw SysError(L"SSL_set1_host failed."); //no more error details
+ throw SysError(formatSystemError("SSL_set1_host", L"", L"Unexpected failure.")); //no more error details
}
const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state()
if (rv != 1)
- throw SysError(formatLastOpenSSLError(L"SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
+ throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
if (caCertFilePath)
{
const long verifyResult = ::SSL_get_verify_result(ssl_);
if (verifyResult != X509_V_OK)
- throw SysError(formatSystemError(L"SSL_get_verify_result", formatX509ErrorCode(verifyResult), L""));
+ throw SysError(formatSystemError("SSL_get_verify_result", formatX509ErrorCode(verifyResult), L""));
}
}
@@ -570,17 +570,20 @@ public:
return 0; //EOF + close_notify alert
warn_static("find a better solution for SSL_read_ex + EOF")
- //"sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0" => obsolete as of OpenSSL 1.1.1e
- //https://github.com/openssl/openssl/issues/10880#issuecomment-575746226
+#if OPENSSL_VERSION_NUMBER == 0x1010105fL //OpenSSL 1.1.1e
const auto ec = ::ERR_peek_last_error();
if (sslError == SSL_ERROR_SSL && ERR_GET_REASON(ec) == SSL_R_UNEXPECTED_EOF_WHILE_READING) //EOF: only expected for HTTP/1.0
return 0;
-
- throw SysError(formatLastOpenSSLError(L"SSL_read_ex") + L' ' + formatSslErrorCode(sslError));
+#else //obsolete handling, at least in OpenSSL 1.1.1e (but valid again with OpenSSL 1.1.1f!)
+ //https://github.com/openssl/openssl/issues/10880#issuecomment-575746226
+ if ((sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0
+ return 0;
+#endif
+ throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + formatSslErrorCode(sslError));
}
assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error!
if (bytesReceived > bytesToRead) //better safe than sorry
- throw SysError(L"SSL_read_ex: buffer overflow.");
+ throw SysError(formatSystemError("SSL_read_ex", L"", L"Buffer overflow."));
return bytesReceived; //"zero indicates end of file"
}
@@ -593,12 +596,12 @@ public:
size_t bytesWritten = 0;
const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten);
if (rv != 1)
- throw SysError(formatLastOpenSSLError(L"SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
+ throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
if (bytesWritten > bytesToWrite)
- throw SysError(L"SSL_write_ex: buffer overflow.");
+ throw SysError(formatSystemError("SSL_write_ex", L"", L"Buffer overflow."));
if (bytesWritten == 0)
- throw SysError(L"SSL_write_ex: zero bytes processed");
+ throw SysError(formatSystemError("SSL_write_ex", L"", L"Zero bytes processed."));
return bytesWritten;
}
@@ -728,7 +731,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
EVP_CIPHER_CTX* cipCtx = ::EVP_CIPHER_CTX_new();
if (!cipCtx)
- throw SysError(L"EVP_CIPHER_CTX_new failed."); //no more error details
+ throw SysError(formatSystemError("EVP_CIPHER_CTX_new", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_CIPHER_CTX_free(cipCtx));
if (::EVP_DecryptInit_ex(cipCtx, //EVP_CIPHER_CTX* ctx,
@@ -736,10 +739,10 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
nullptr, //ENGINE* impl,
key, //const unsigned char* key, => implied length of 256 bit!
nullptr) != 1) //const unsigned char* iv
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptInit_ex"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptInit_ex"));
if (::EVP_CIPHER_CTX_set_padding(cipCtx, 0 /*padding*/) != 1)
- throw SysError(L"EVP_CIPHER_CTX_set_padding failed."); //no more error details
+ throw SysError(formatSystemError("EVP_CIPHER_CTX_set_padding", L"", L"Unexpected failure.")); //no more error details
privateBlob.resize(privateBlobEnc.size() + ::EVP_CIPHER_block_size(EVP_aes_256_cbc()));
//"EVP_DecryptUpdate() should have room for (inl + cipher_block_size) bytes"
@@ -750,13 +753,13 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
&decLen1, //int* outl,
reinterpret_cast<const unsigned char*>(privateBlobEnc.c_str()), //const unsigned char* in,
static_cast<int>(privateBlobEnc.size())) != 1) //int inl
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptUpdate"));
int decLen2 = 0;
if (::EVP_DecryptFinal_ex(cipCtx, //EVP_CIPHER_CTX* ctx,
reinterpret_cast<unsigned char*>(&privateBlob[decLen1]), //unsigned char* outm,
&decLen2) != 1) //int* outl
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptFinal_ex"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptFinal_ex"));
privateBlob.resize(decLen1 + decLen2);
}
@@ -790,11 +793,11 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
static_cast<int>(macData.size()), //int n,
reinterpret_cast<unsigned char*>(md), //unsigned char* md,
&mdLen)) //unsigned int* md_len
- throw SysError(L"HMAC failed."); //no more error details
+ throw SysError(formatSystemError("HMAC", L"", L"Unexpected failure.")); //no more error details
const bool hashValid = mac == std::string_view(md, mdLen);
if (!hashValid)
- throw SysError(keyEncrypted ? L"MAC validation failed: wrong passphrase or corrupted key" : L"MAC validation failed: corrupted key");
+ throw SysError(formatSystemError("HMAC", L"", keyEncrypted ? L"Validation failed: wrong passphrase or corrupted key" : L"Validation failed: corrupted key"));
//----------------------------------------------------------
auto extractString = [](auto& it, auto itEnd)
@@ -823,7 +826,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
{
BIGNUM* bn = ::BN_new();
if (!bn)
- throw SysError(formatLastOpenSSLError(L"BN_new"));
+ throw SysError(formatLastOpenSSLError("BN_new"));
return std::unique_ptr<BIGNUM, BnFree>(bn);
};
@@ -833,7 +836,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
BIGNUM* bn = ::BN_bin2bn(reinterpret_cast<const unsigned char*>(&bytes[0]), static_cast<int>(bytes.size()), nullptr);
if (!bn)
- throw SysError(formatLastOpenSSLError(L"BN_bin2bn"));
+ throw SysError(formatLastOpenSSLError("BN_bin2bn"));
return std::unique_ptr<BIGNUM, BnFree>(bn);
};
@@ -866,43 +869,43 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
BN_CTX* bnCtx = BN_CTX_new();
if (!bnCtx)
- throw SysError(formatLastOpenSSLError(L"BN_CTX_new"));
+ throw SysError(formatLastOpenSSLError("BN_CTX_new"));
ZEN_ON_SCOPE_EXIT(::BN_CTX_free(bnCtx));
if (::BN_sub(tmp.get(), p.get(), BN_value_one()) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_sub"));
+ throw SysError(formatLastOpenSSLError("BN_sub"));
if (::BN_mod(dmp1.get(), d.get(), tmp.get(), bnCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_mod"));
+ throw SysError(formatLastOpenSSLError("BN_mod"));
if (::BN_sub(tmp.get(), q.get(), BN_value_one()) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_sub"));
+ throw SysError(formatLastOpenSSLError("BN_sub"));
if (::BN_mod(dmq1.get(), d.get(), tmp.get(), bnCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_mod"));
+ throw SysError(formatLastOpenSSLError("BN_mod"));
//----------------------------------------------------------
RSA* rsa = ::RSA_new();
if (!rsa)
- throw SysError(formatLastOpenSSLError(L"RSA_new"));
+ throw SysError(formatLastOpenSSLError("RSA_new"));
ZEN_ON_SCOPE_EXIT(::RSA_free(rsa));
if (::RSA_set0_key(rsa, n.release(), e.release(), d.release()) != 1) //pass BIGNUM ownership
- throw SysError(formatLastOpenSSLError(L"RSA_set0_key"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_key"));
if (::RSA_set0_factors(rsa, p.release(), q.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"RSA_set0_factors"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_factors"));
if (::RSA_set0_crt_params(rsa, dmp1.release(), dmq1.release(), iqmp.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"RSA_set0_crt_params"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_crt_params"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -918,22 +921,22 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
DSA* dsa = ::DSA_new();
if (!dsa)
- throw SysError(formatLastOpenSSLError(L"DSA_new"));
+ throw SysError(formatLastOpenSSLError("DSA_new"));
ZEN_ON_SCOPE_EXIT(::DSA_free(dsa));
if (::DSA_set0_pqg(dsa, p.release(), q.release(), g.release()) != 1) //pass BIGNUM ownership
- throw SysError(formatLastOpenSSLError(L"DSA_set0_pqg"));
+ throw SysError(formatLastOpenSSLError("DSA_set0_pqg"));
if (::DSA_set0_key(dsa, pub.release(), pri.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"DSA_set0_key"));
+ throw SysError(formatLastOpenSSLError("DSA_set0_key"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_DSA(evp, dsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_DSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_DSA"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -963,16 +966,16 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
EC_KEY* ecKey = ::EC_KEY_new_by_curve_name(curveNid);
if (!ecKey)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_new_by_curve_name"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_new_by_curve_name"));
ZEN_ON_SCOPE_EXIT(::EC_KEY_free(ecKey));
const EC_GROUP* ecGroup = ::EC_KEY_get0_group(ecKey);
if (!ecGroup)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_get0_group"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_get0_group"));
EC_POINT* ecPoint = ::EC_POINT_new(ecGroup);
if (!ecPoint)
- throw SysError(formatLastOpenSSLError(L"EC_POINT_new"));
+ throw SysError(formatLastOpenSSLError("EC_POINT_new"));
ZEN_ON_SCOPE_EXIT(::EC_POINT_free(ecPoint));
if (::EC_POINT_oct2point(ecGroup, //const EC_GROUP* group,
@@ -980,21 +983,21 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
reinterpret_cast<const unsigned char*>(&pointStream[0]), //const unsigned char* buf,
pointStream.size(), //size_t len,
nullptr) != 1) //BN_CTX* ctx
- throw SysError(formatLastOpenSSLError(L"EC_POINT_oct2point"));
+ throw SysError(formatLastOpenSSLError("EC_POINT_oct2point"));
if (::EC_KEY_set_public_key(ecKey, ecPoint) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_set_public_key"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_set_public_key"));
if (::EC_KEY_set_private_key(ecKey, pri.get()) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_set_private_key"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_set_private_key"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_EC_KEY(evp, ecKey) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_EC_KEY"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_EC_KEY"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -1009,7 +1012,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
reinterpret_cast<const unsigned char*>(&priStream[0]), //const unsigned char* priv,
priStream.size()); //size_t len
if (!evpPriv)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new_raw_private_key"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new_raw_private_key"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evpPriv));
return keyToStream(evpPriv, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
diff --git a/zen/perf.h b/zen/perf.h
index 9f368016..33005e9f 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -32,7 +32,7 @@ namespace zen
// => wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp
// => whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter():
// MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot"
-// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408#How_often_does_QPC_roll_over_
+// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#How_often_does_QPC_roll_over
//
// => using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280
//
diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp
index 5aa9a0ce..b7b4e029 100644
--- a/zen/process_priority.cpp
+++ b/zen/process_priority.cpp
@@ -15,7 +15,7 @@ struct PreventStandby::Impl {};
PreventStandby::PreventStandby() {}
PreventStandby::~PreventStandby() {}
-//solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
+//solution for GNOME?: https://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
struct ScheduleForBackgroundProcessing::Impl {};
ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {}
@@ -25,7 +25,7 @@ ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {}
struct ScheduleForBackgroundProcessing
{
- required functions ioprio_get/ioprio_set are not part of glibc: https://linux.die.net/man/2/ioprio_set
- - and probably never will: http://sourceware.org/bugzilla/show_bug.cgi?id=4464
+ - and probably never will: https://sourceware.org/bugzilla/show_bug.cgi?id=4464
- /usr/include/linux/ioprio.h not available on Ubuntu, so we can't use it instead
ScheduleForBackgroundProcessing() : oldIoPrio(getIoPriority(IOPRIO_WHO_PROCESS, ::getpid()))
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index f4fd870b..4d6ea1fd 100644
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -31,12 +31,8 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
if (!type)
return false;
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
- if (!error)
- throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this
-
//implement same behavior as in Windows: if recycler is not existing, delete permanently
- if (error->code == G_IO_ERROR_NOT_SUPPORTED)
+ if (error && error->code == G_IO_ERROR_NOT_SUPPORTED)
{
if (*type == ItemType::FOLDER)
removeDirectoryPlainRecursion(itemPath); //throw FileError
@@ -45,9 +41,10 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
return true;
}
- throw FileError(errorMsg, formatSystemError(L"g_file_trash",
- replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)),
- utfTo<std::wstring>(error->message)));
+ throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)),
+ formatSystemError("g_file_trash",
+ error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"",
+ error ? utfTo<std::wstring>(error->message) : L"Unknown error."));
//g_quark_to_string(error->domain)
}
return true;
diff --git a/zen/shell_execute.cpp b/zen/shell_execute.cpp
new file mode 100644
index 00000000..63696568
--- /dev/null
+++ b/zen/shell_execute.cpp
@@ -0,0 +1,250 @@
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "shell_execute.h"
+#include <chrono>
+#include "guid.h"
+#include "file_access.h"
+#include "file_io.h"
+
+ #include <unistd.h> //fork, pipe
+ #include <sys/wait.h> //waitpid
+ #include <fcntl.h>
+
+using namespace zen;
+
+
+std::vector<Zstring> zen::parseCommandline(const Zstring& cmdLine)
+{
+ std::vector<Zstring> args;
+ //"Parsing C++ Command-Line Arguments": https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments
+ //we do the job ourselves! both wxWidgets and ::CommandLineToArgvW() parse "C:\" "D:\" as single line C:\" D:\"
+ //-> "solution": we just don't support protected quotation mark!
+
+ auto itStart = cmdLine.end(); //end() means: no token
+ for (auto it = cmdLine.begin(); it != cmdLine.end(); ++it)
+ if (*it == Zstr(' ')) //space commits token
+ {
+ if (itStart != cmdLine.end())
+ {
+ args.emplace_back(itStart, it);
+ itStart = cmdLine.end(); //expect consecutive blanks!
+ }
+ }
+ else
+ {
+ //start new token
+ if (itStart == cmdLine.end())
+ itStart = it;
+
+ if (*it == Zstr('"'))
+ {
+ it = std::find(it + 1, cmdLine.end(), Zstr('"'));
+ if (it == cmdLine.end())
+ break;
+ }
+ }
+ if (itStart != cmdLine.end())
+ args.emplace_back(itStart, cmdLine.end());
+
+ for (Zstring& str : args)
+ if (str.size() >= 2 && startsWith(str, Zstr('"')) && endsWith(str, Zstr('"')))
+ str = Zstring(str.c_str() + 1, str.size() - 2);
+
+ return args;
+}
+
+
+
+
+std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut
+{
+ const Zstring tempFilePath = appendSeparator(getTempFolderPath()) + //throw FileError
+ Zstr("FFS-") + utfTo<Zstring>(formatAsHexString(generateGUID()));
+ /* can't use popen(): does NOT return the exit code on Linux (despite the documentation!), although it works correctly on macOS
+ => use pipes instead: https://linux.die.net/man/2/waitpid
+ bonus: no need for "2>&1" to redirect STDERR to STDOUT
+
+ What about premature exit via SysErrorTimeOut?
+ Linux: child process' end of the pipe *still works* even after the parent process is gone:
+ There does not seem to be any output buffer size limit + no observable strain on system memory or disk space! :)
+ macOS: child process exits if parent end of pipe is closed: fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu..........
+
+ => solution: buffer output in temporary file
+
+ Unresolved problem: premature exit via SysErrorTimeOut (=> no waitpid()) creates zombie proceses:
+ "As long as a zombie is not removed from the system via a wait,
+ it will consume a slot in the kernel process table, and if this table fills,
+ it will not be possible to create further processes." */
+
+ const int EC_CHILD_LAUNCH_FAILED = 120; //avoid 127: used by the system, e.g. failure to execute due to missing .so file
+
+ const int fdTempFile = ::open(tempFilePath.c_str(), O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC,
+ S_IRUSR | S_IWUSR); //0600
+ if (fdTempFile == -1)
+ THROW_LAST_SYS_ERROR("open");
+ auto guardTmpFile = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdTempFile); });
+
+ //"deleting while handles are open" == FILE_FLAG_DELETE_ON_CLOSE
+ if (::unlink(tempFilePath.c_str()) != 0)
+ THROW_LAST_SYS_ERROR("unlink");
+
+ //--------------------------------------------------------------
+ //waitpid() is a useless pile of garbage without time out => check EOF from dummy pipe instead
+ int pipe[2] = {};
+ if (::pipe2(pipe, O_CLOEXEC) != 0)
+ THROW_LAST_SYS_ERROR("pipe2");
+
+
+ const int fdLifeSignR = pipe[0]; //for parent process
+ const int fdLifeSignW = pipe[1]; //for child process
+ ZEN_ON_SCOPE_EXIT(::close(fdLifeSignR));
+ auto guardFdLifeSignW = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdLifeSignW ); });
+ //--------------------------------------------------------------
+
+ //follow implemenation of ::system(): https://github.com/lattera/glibc/blob/master/sysdeps/posix/system.c
+ const pid_t pid = ::fork();
+ if (pid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait
+ THROW_LAST_SYS_ERROR("fork");
+
+ if (pid == 0) //child process
+ try
+ {
+ //first task: set STDOUT redirection in case an error needs to be reported
+ if (::dup2(fdTempFile, STDOUT_FILENO) != STDOUT_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDOUT)");
+
+ if (::dup2(fdTempFile, STDERR_FILENO) != STDERR_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDERR)");
+
+ //avoid blocking scripts waiting for user input
+ // => appending " < /dev/null" is not good enough! e.g. hangs for: read -p "still hanging here"; echo fuuuuu...
+ const int fdDevNull = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
+ if (fdDevNull == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
+ THROW_LAST_SYS_ERROR("open(/dev/null)");
+ ZEN_ON_SCOPE_EXIT(::close(fdDevNull));
+
+ if (::dup2(fdDevNull, STDIN_FILENO) != STDIN_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDIN)");
+
+ //*leak* the fd and have it closed automatically on child process exit after execv()
+ if (::dup(fdLifeSignW) == -1) //O_CLOEXEC does NOT propagate with dup()
+ THROW_LAST_SYS_ERROR("dup(fdLifeSignW)");
+
+
+ const char* argv[] = { "sh", "-c", cmdLine.c_str(), nullptr };
+ /*int rv =*/::execv("/bin/sh", const_cast<char**>(argv)); //only returns if an error occurred
+ //safe to cast away const: https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
+ // "The statement about argv[] and envp[] being constants is included to make explicit to future
+ // writers of language bindings that these objects are completely constant. Due to a limitation of
+ // the ISO C standard, it is not possible to state that idea in standard C."
+ THROW_LAST_SYS_ERROR("execv");
+ }
+ catch (const SysError& e)
+ {
+ ::puts(utfTo<std::string>(e.toString()).c_str());
+ ::fflush(stdout); //note: stderr is unbuffered by default
+ ::_exit(EC_CHILD_LAUNCH_FAILED); //[!] avoid flushing I/O buffers or doing other clean up from child process like with "exit()"!
+ }
+ //else: parent process
+
+
+ if (timeoutMs)
+ {
+ guardFdLifeSignW.dismiss();
+ ::close(fdLifeSignW); //[!] make sure we get EOF when fd is closed by child!
+
+ if (::fcntl(fdLifeSignR, F_SETFL, O_NONBLOCK) != 0)
+ THROW_LAST_SYS_ERROR("fcntl(O_NONBLOCK)");
+
+ const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs);
+ for (;;) //EINTR handling? => allow interrupt!?
+ {
+ //read until EAGAIN
+ char buf[16];
+ const ssize_t bytesRead = ::read(fdLifeSignR, buf, sizeof(buf));
+ if (bytesRead < 0)
+ {
+ if (errno != EAGAIN)
+ THROW_LAST_SYS_ERROR("read");
+ }
+ else if (bytesRead > 0)
+ throw SysError(formatSystemError("read", L"", L"Unexpected data."));
+ else //bytesRead == 0: EOF
+ break;
+
+ //wait for stream input
+ const auto now = std::chrono::steady_clock::now();
+ if (now > endTime)
+ throw SysErrorTimeOut(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", *timeoutMs / 1000));
+
+ const auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - now).count();
+
+ struct ::timeval tv = {};
+ tv.tv_sec = static_cast<long>(waitTimeMs / 1000);
+ tv.tv_usec = static_cast<long>(waitTimeMs - tv.tv_sec * 1000) * 1000;
+
+ fd_set rfd = {}; //includes FD_ZERO
+ FD_SET(fdLifeSignR, &rfd);
+
+ if (const int rv = ::select(fdLifeSignR + 1, //int nfds,
+ &rfd, //fd_set* readfds,
+ nullptr, //fd_set* writefds,
+ nullptr, //fd_set* exceptfds,
+ &tv); //struct timeval* timeout
+ rv < 0)
+ THROW_LAST_SYS_ERROR("select");
+ else if (rv == 0)
+ throw SysErrorTimeOut(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", *timeoutMs / 1000));
+ }
+ }
+
+ //https://linux.die.net/man/2/waitpid
+ int statusCode = 0;
+ if (::waitpid(pid, //pid_t pid
+ &statusCode, //int* status
+ 0) != pid) //int options
+ THROW_LAST_SYS_ERROR("waitpid");
+
+
+ if (::lseek(fdTempFile, 0, SEEK_SET) != 0)
+ THROW_LAST_SYS_ERROR("lseek");
+
+ guardTmpFile.dismiss();
+ FileInput streamIn(fdTempFile, tempFilePath, nullptr /*notifyUnbufferedIO*/); //takes ownership!
+ const std::wstring output = utfTo<std::wstring>(bufferedLoad<std::string>(streamIn)); //throw FileError
+
+
+ if (!WIFEXITED(statusCode)) //signalled, crashed?
+ throw SysError(formatSystemError("waitpid", WIFSIGNALED(statusCode) ?
+ L"Killed by signal " + numberTo<std::wstring>(WTERMSIG(statusCode)) :
+ L"Exit status " + numberTo<std::wstring>(statusCode),
+ utfTo<std::wstring>(trimCpy(output))));
+
+ const int exitCode = WEXITSTATUS(statusCode); //precondition: "WIFEXITED() == true"
+ if (exitCode == EC_CHILD_LAUNCH_FAILED || //child process should already have provided details to STDOUT
+ exitCode == 127) //details should have been streamed to STDERR: used by /bin/sh, e.g. failure to execute due to missing .so file
+ throw SysError(utfTo<std::wstring>(trimCpy(output)));
+
+ return { exitCode, output };
+}
+
+
+void zen::openWithDefaultApp(const Zstring& itemPath) //throw FileError
+{
+ try
+ {
+ const Zstring cmdTemplate = R"(xdg-open "%x")"; //doesn't block => no need for time out!
+ const Zstring cmdLine = replaceCpy(cmdTemplate, Zstr("%x"), itemPath);
+
+ if (const auto [exitCode, output] = consoleExecute(cmdLine, std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut)
+ exitCode != 0)
+ throw SysError(formatSystemError(utfTo<std::string>(cmdTemplate), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(itemPath)), e.toString()); }
+}
+
+
diff --git a/zen/shell_execute.h b/zen/shell_execute.h
index faea4bd9..b80cf2ba 100644
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -9,105 +9,18 @@
#include "file_error.h"
- #include <unistd.h> //fork()
- #include <stdlib.h> //::system()
-
namespace zen
{
-//launch commandline and report errors via popup dialog
-//Windows: COM needs to be initialized before calling this function!
-enum class ExecutionType
-{
- sync,
- async
-};
-
-namespace
-{
-
-
-int shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) //throw FileError
-{
- /*
- we cannot use wxExecute due to various issues:
- - screws up encoding on OS X for non-ASCII characters
- - does not provide any reasonable error information
- - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows
- */
- if (type == ExecutionType::sync)
- {
- //Posix ::system() - execute a shell command
- const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", etc...
- if (rv == -1 || WEXITSTATUS(rv) == 127)
- throw FileError(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(command));
- //https://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
- //Bonus: For an incorrect command line /bin/sh also returns with 127!
-
- return /*int exitCode = */ WEXITSTATUS(rv);
- }
- else
- {
- //follow implemenation of ::system() except for waitpid():
- const pid_t pid = ::fork();
- if (pid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait
- THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(command), L"fork");
-
- if (pid == 0) //child process
- {
- const char* argv[] = { "sh", "-c", command.c_str(), nullptr };
- /*int rv =*/::execv("/bin/sh", const_cast<char**>(argv));
- //safe to cast away const: http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
- // "The statement about argv[] and envp[] being constants is included to make explicit to future
- // writers of language bindings that these objects are completely constant. Due to a limitation of
- // the ISO C standard, it is not possible to state that idea in standard C."
-
- //"execv() only returns if an error has occurred. The return value is -1, and errno is set to indicate the error."
- ::_exit(127); //[!] avoid flushing I/O buffers or doing other clean up from child process like with "exit(127)"!
- }
- //else //parent process
- return 0;
- }
-}
-
-
-std::string getCommandOutput(const Zstring& command) //throw SysError
-{
- //https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/popen.3.html
- FILE* pipe = ::popen(command.c_str(), "r");
- if (!pipe)
- THROW_LAST_SYS_ERROR(L"popen");
- ZEN_ON_SCOPE_EXIT(::pclose(pipe));
+std::vector<Zstring> parseCommandline(const Zstring& cmdLine);
- std::string output;
- const size_t blockSize = 64 * 1024;
- do
- {
- output.resize(output.size() + blockSize);
- //caveat: SIGCHLD is NOT ignored under macOS debugger => EINTR inside fread() => call ::siginterrupt(SIGCHLD, false) during startup
- const size_t bytesRead = ::fread(&*(output.end() - blockSize), 1, blockSize, pipe);
- if (::ferror(pipe))
- THROW_LAST_SYS_ERROR(L"fread");
+DEFINE_NEW_SYS_ERROR(SysErrorTimeOut)
+[[nodiscard]] std::pair<int /*exit code*/, std::wstring> consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs); //throw SysError, SysErrorTimeOut
+/* limitations: Windows: cmd.exe returns exit code 1 if file not found (instead of throwing SysError) => nodiscard!
+ Linux/macOS: SysErrorTimeOut leaves zombie process behind */
- if (bytesRead > blockSize)
- throw SysError(L"fread: buffer overflow");
-
- if (bytesRead < blockSize)
- output.resize(output.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics
- }
- while (!::feof(pipe));
-
- return output;
-}
-}
-
-
-inline
-void openWithDefaultApplication(const Zstring& itemPath) //throw FileError
-{
- shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::async, false /*hideConsole*/); //throw FileError
-}
+void openWithDefaultApp(const Zstring& itemPath); //throw FileError
}
#endif //SHELL_EXECUTE_H_23482134578134134
diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp
index 89da55ee..21e24527 100644
--- a/zen/shutdown.cpp
+++ b/zen/shutdown.cpp
@@ -15,19 +15,31 @@ using namespace zen;
void zen::shutdownSystem() //throw FileError
{
- //https://linux.die.net/man/2/reboot => needs admin rights!
-
- //"systemctl" should work without admin rights:
- shellExecute("systemctl poweroff", ExecutionType::sync, false/*hideConsole*/); //throw FileError
-
+ try
+ {
+ //https://linux.die.net/man/2/reboot => needs admin rights!
+ //"systemctl" should work without admin rights:
+ const auto [exitCode, output] = consoleExecute("systemctl poweroff", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut)
+ if (!trimCpy(output).empty()) //see comment in suspendSystem()
+ throw SysError(output);
+
+ }
+ catch (const SysError& e) { throw FileError(_("Unable to shut down the system."), e.toString()); }
}
void zen::suspendSystem() //throw FileError
{
- //"systemctl" should work without admin rights:
- shellExecute("systemctl suspend", ExecutionType::sync, false/*hideConsole*/); //throw FileError
-
+ try
+ {
+ //"systemctl" should work without admin rights:
+ const auto [exitCode, output] = consoleExecute("systemctl suspend", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut)
+ //why does "systemctl suspend" return exit code 1 despite apparent success!??
+ if (!trimCpy(output).empty()) //at least we can assume "no output" on success
+ throw SysError(output);
+
+ }
+ catch (const SysError& e) { throw FileError(_("Unable to shut down the system."), e.toString()); }
}
diff --git a/zen/socket.h b/zen/socket.h
index 3bd0a2a0..f1d26450 100644
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -42,19 +42,19 @@ public:
const int rcGai = ::getaddrinfo(server.c_str(), serviceName.c_str(), &hints, &servinfo);
if (rcGai != 0)
- throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
if (!servinfo)
- throw SysError(L"getaddrinfo: empty server info");
+ throw SysError(formatSystemError("getaddrinfo", L"", L"Empty server info."));
const auto getConnectedSocket = [](const auto& /*::addrinfo*/ ai)
{
SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol);
if (testSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"socket");
+ THROW_LAST_SYS_ERROR_WSA("socket");
ZEN_ON_SCOPE_FAIL(closeSocket(testSocket));
if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"connect");
+ THROW_LAST_SYS_ERROR_WSA("connect");
return testSocket;
};
@@ -102,10 +102,10 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro
break;
}
if (bytesReceived < 0)
- THROW_LAST_SYS_ERROR_WSA(L"recv");
+ THROW_LAST_SYS_ERROR_WSA("recv");
if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry
- throw SysError(L"recv: buffer overflow.");
+ throw SysError(formatSystemError("recv", L"", L"Buffer overflow."));
return bytesReceived; //"zero indicates end of file"
}
@@ -127,11 +127,11 @@ size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite
break;
}
if (bytesWritten < 0)
- THROW_LAST_SYS_ERROR_WSA(L"send");
+ THROW_LAST_SYS_ERROR_WSA("send");
if (bytesWritten > static_cast<int>(bytesToWrite))
- throw SysError(L"send: buffer overflow.");
+ throw SysError(formatSystemError("send", L"", L"Buffer overflow."));
if (bytesWritten == 0)
- throw SysError(L"send: zero bytes processed");
+ throw SysError(formatSystemError("send", L"", L"Zero bytes processed."));
return bytesWritten;
}
@@ -143,7 +143,7 @@ inline
void shutdownSocketSend(SocketType socket) //throw SysError
{
if (::shutdown(socket, SHUT_WR) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"shutdown");
+ THROW_LAST_SYS_ERROR_WSA("shutdown");
}
}
diff --git a/zen/string_base.h b/zen/string_base.h
index 42e1bdf3..5922c3ff 100644
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -566,8 +566,8 @@ const Char& Zbase<Char, SP>::operator[](size_t pos) const
template <class Char, template <class> class SP> inline
Char& Zbase<Char, SP>::operator[](size_t pos)
{
- assert(pos < length()); //design by contract! no runtime check!
reserve(length()); //make unshared!
+ assert(pos < length()); //design by contract! no runtime check!
return rawStr_[pos];
}
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 40a4ea52..cd26f5fd 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -83,6 +83,7 @@ template <class Num, class S> Num stringTo(const S& str);
std::pair<char, char> hexify (unsigned char c, bool upperCase = true);
char unhexify(char high, char low);
+std::string formatAsHexString(const std::string& blob); //bytes -> (human-readable) hex string
template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf()
@@ -848,6 +849,20 @@ char unhexify(char high, char low)
}
+inline
+std::string formatAsHexString(const std::string& blob)
+{
+ std::string output;
+ for (const char c : blob)
+ {
+ const auto [high, low] = hexify(c, false /*upperCase*/);
+ output += high;
+ output += low;
+ }
+ return output;
+}
+
+
}
#endif //STRING_TOOLS_H_213458973046
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
index 2393013e..077fd4b3 100644
--- a/zen/symlink_target.h
+++ b/zen/symlink_target.h
@@ -42,9 +42,9 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro
const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE);
if (bytesWritten < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), "readlink");
if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation, not an error for readlink!
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink: buffer truncated.");
+ throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), formatSystemError("readlink", L"", L"Buffer truncated."));
return Zstring(&buffer[0], bytesWritten); //readlink does not append 0-termination!
}
@@ -55,7 +55,7 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
using namespace zen;
char* targetPath = ::realpath(linkPath.c_str(), nullptr);
if (!targetPath)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"realpath");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), "realpath");
ZEN_ON_SCOPE_EXIT(::free(targetPath));
return targetPath;
}
diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp
index b802780b..f9747d45 100644
--- a/zen/sys_error.cpp
+++ b/zen/sys_error.cpp
@@ -162,29 +162,30 @@ std::wstring formatSystemErrorCode(ErrorCode ec)
ZEN_CHECK_CASE_FOR_CONSTANT(ERFKILL);
ZEN_CHECK_CASE_FOR_CONSTANT(EHWPOISON);
default:
- return replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec));
+ return replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(ec));
}
}
}
-std::wstring zen::formatSystemError(const std::wstring& functionName, ErrorCode ec)
+std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode ec)
{
return formatSystemError(functionName, formatSystemErrorCode(ec), getSystemErrorDescription(ec));
}
-std::wstring zen::formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
+std::wstring zen::formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
{
- std::wstring output = errorCode + L':';
+ std::wstring output = errorCode;
const std::wstring errorMsgFmt = trimCpy(errorMsg);
- if (!errorMsgFmt.empty())
- {
- output += L' ';
- output += errorMsgFmt;
- }
+ if (!errorCode.empty() && !errorMsgFmt.empty())
+ output += L": ";
+
+ output += errorMsgFmt;
+
+ if (!functionName.empty())
+ output += L" [" + utfTo<std::wstring>(functionName) + L']';
- output += L" [" + functionName + L']';
- return output;
+ return trimCpy(output);
}
diff --git a/zen/sys_error.h b/zen/sys_error.h
index 6bef45ea..2dd3c188 100644
--- a/zen/sys_error.h
+++ b/zen/sys_error.h
@@ -22,8 +22,8 @@ namespace zen
ErrorCode getLastError();
-std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
-std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec);
+std::wstring formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
+std::wstring formatSystemError(const std::string& functionName, ErrorCode ec);
//A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"!
diff --git a/zen/system.cpp b/zen/system.cpp
index 9401b94f..d9a169c7 100644
--- a/zen/system.cpp
+++ b/zen/system.cpp
@@ -29,7 +29,7 @@ std::wstring zen::getUserName() //throw FileError
struct passwd buffer2 = {};
struct passwd* pwsEntry = nullptr;
if (::getpwuid_r(userIdNo, &buffer2, &buffer[0], buffer.size(), &pwsEntry) != 0) //getlogin() is deprecated and not working on Ubuntu at all!!!
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getpwuid_r");
+ THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwuid_r");
if (!pwsEntry)
throw FileError(_("Cannot get process information."), L"no login found"); //should not happen?
@@ -96,9 +96,22 @@ std::wstring zen::getOsDescription() //throw FileError
{
try
{
- const std::string osName = trimCpy(getCommandOutput("lsb_release --id -s" )); //throw SysError
- const std::string osVersion = trimCpy(getCommandOutput("lsb_release --release -s")); //
- return utfTo<std::wstring>(osName + ' ' + osVersion); //e.g. "CentOS 7.7.1908"
+ std::wstring osName;
+ std::wstring osVersion;
+
+ if (const auto [exitCode, output] = consoleExecute("lsb_release --id -s", std::nullopt); //throw SysError
+ exitCode != 0)
+ throw SysError(formatSystemError("lsb_release --id", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
+ else
+ osName = trimCpy(output);
+
+ if (const auto [exitCode, output] = consoleExecute("lsb_release --release -s", std::nullopt); //throw SysError
+ exitCode != 0)
+ throw SysError(formatSystemError("lsb_release --release", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output));
+ else
+ osVersion = trimCpy(output);
+
+ return osName + L' ' + osVersion; //e.g. "CentOS 7.7.1908"
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
diff --git a/zen/time.h b/zen/time.h
index 9718e5f6..d3aef36f 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -121,7 +121,7 @@ bool isValid(const std::tm& t)
auto inRange = [](int value, int minVal, int maxVal) { return minVal <= value && value <= maxVal; };
- //http://www.cplusplus.com/reference/clibrary/ctime/tm/
+ //https://www.cplusplus.com/reference/clibrary/ctime/tm/
return inRange(t.tm_sec, 0, 61) &&
inRange(t.tm_min, 0, 59) &&
inRange(t.tm_hour, 0, 23) &&
diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp
index 685843c3..dba890ee 100644
--- a/zen/zlib_wrap.cpp
+++ b/zen/zlib_wrap.cpp
@@ -55,7 +55,7 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_
// Z_MEM_ERROR: not enough memory
// Z_BUF_ERROR: not enough room in the output buffer
if (rv != Z_OK || bufferSize > trgLen)
- throw SysError(formatSystemError(L"zlib compress2", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib compress2", formatZlibStatusCode(rv), L""));
return bufferSize;
}
@@ -73,7 +73,7 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz
// Z_BUF_ERROR: not enough room in the output buffer
// Z_DATA_ERROR: input data was corrupted or incomplete
if (rv != Z_OK || bufferSize > trgLen)
- throw SysError(formatSystemError(L"zlib uncompress", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib uncompress", formatZlibStatusCode(rv), L""));
return bufferSize;
}
@@ -98,7 +98,7 @@ public:
memLevel, //int memLevel
Z_DEFAULT_STRATEGY); //int strategy
if (rv != Z_OK)
- throw SysError(formatSystemError(L"zlib deflateInit2", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib deflateInit2", formatZlibStatusCode(rv), L""));
}
~Impl()
@@ -133,7 +133,7 @@ public:
if (rv == Z_STREAM_END)
return bytesToRead - gzipStream_.avail_out;
if (rv != Z_OK)
- throw SysError(formatSystemError(L"zlib deflate", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib deflate", formatZlibStatusCode(rv), L""));
if (gzipStream_.avail_out == 0)
return bytesToRead;
diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h
index 3db609da..41d7428a 100644
--- a/zen/zlib_wrap.h
+++ b/zen/zlib_wrap.h
@@ -113,7 +113,7 @@ BinContainer decompress(const BinContainer& stream) //throw SysError
&*contOut.begin(),
static_cast<size_t>(uncompressedSize)); //throw SysError
if (bytesWritten != static_cast<size_t>(uncompressedSize))
- throw SysError(L"zlib error: bytes written != uncompressed size");
+ throw SysError(formatSystemError("zlib_decompress", L"", L"bytes written != uncompressed size."));
}
return contOut;
}
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index 82082df0..8b16e02d 100644
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -59,7 +59,7 @@ Zstring getUnicodeNormalForm(const Zstring& str)
{
gchar* outStr = ::g_utf8_normalize(str.c_str(), str.length(), G_NORMALIZE_DEFAULT_COMPOSE);
if (!outStr)
- throw SysError(L"g_utf8_normalize: conversion failed. (" + utfTo<std::wstring>(str) + L')');
+ throw SysError(formatSystemError("g_utf8_normalize(" + utfTo<std::string>(str) + ')', L"", L"Conversion failed."));
ZEN_ON_SCOPE_EXIT(::g_free(outStr));
return outStr;
diff --git a/zen/zstring.h b/zen/zstring.h
index d5d8c588..e34d14a3 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -35,7 +35,7 @@ Zstring makeUpperCopy(const Zstring& str);
Zstring getUnicodeNormalForm(const Zstring& str);
// "In fact, Unicode declares that there is an equivalence relationship between decomposed and composed sequences,
// and conformant software should not treat canonically equivalent sequences, whether composed or decomposed or something in between, as different."
-// http://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html
+// https://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html
struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs);} };
bgstack15