summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog.txt10
-rw-r--r--FreeFileSync/Build/Resources/Icons.zipbin354626 -> 354823 bytes
-rw-r--r--FreeFileSync/Build/Resources/Languages.zipbin558238 -> 527281 bytes
-rw-r--r--FreeFileSync/Build/Resources/cacert.pem32
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.cpp48
-rw-r--r--FreeFileSync/Source/RealTimeSync/gui_generated.h7
-rw-r--r--FreeFileSync/Source/RealTimeSync/main_dlg.cpp2
-rw-r--r--FreeFileSync/Source/RealTimeSync/monitor.cpp2
-rw-r--r--FreeFileSync/Source/afs/abstract.h16
-rw-r--r--FreeFileSync/Source/afs/ftp.cpp35
-rw-r--r--FreeFileSync/Source/afs/gdrive.cpp38
-rw-r--r--FreeFileSync/Source/afs/native.cpp3
-rw-r--r--FreeFileSync/Source/afs/sftp.cpp50
-rw-r--r--FreeFileSync/Source/application.cpp6
-rw-r--r--FreeFileSync/Source/base/db_file.cpp8
-rw-r--r--FreeFileSync/Source/base/path_filter.cpp2
-rw-r--r--FreeFileSync/Source/base/synchronization.cpp2
-rw-r--r--FreeFileSync/Source/localization.cpp66
-rw-r--r--FreeFileSync/Source/parse_lng.h140
-rw-r--r--FreeFileSync/Source/ui/cfg_grid.cpp28
-rw-r--r--FreeFileSync/Source/ui/cfg_grid.h13
-rw-r--r--FreeFileSync/Source/ui/folder_pair.h108
-rw-r--r--FreeFileSync/Source/ui/gui_generated.cpp12
-rw-r--r--FreeFileSync/Source/ui/log_panel.cpp4
-rw-r--r--FreeFileSync/Source/ui/main_dlg.cpp13
-rw-r--r--FreeFileSync/Source/ui/small_dlgs.cpp2
-rw-r--r--FreeFileSync/Source/ui/sync_cfg.cpp4
-rw-r--r--FreeFileSync/Source/ui/version_check.cpp14
-rw-r--r--FreeFileSync/Source/version/version.h2
-rw-r--r--zen/file_path.cpp2
-rw-r--r--zen/file_traverser.cpp2
-rw-r--r--zen/http.cpp4
-rw-r--r--zen/json.h8
-rw-r--r--zen/string_tools.h6
-rw-r--r--zen/sys_version.cpp4
-rw-r--r--zen/zstring.cpp2
-rw-r--r--zenXml/zenxml/parser.h12
37 files changed, 442 insertions, 265 deletions
diff --git a/Changelog.txt b/Changelog.txt
index 01773371..9e4c4fc9 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,13 @@
+FreeFileSync 13.9 [2024-12-07]
+------------------------------
+Fixed CURLE_SEND_ERROR: OpenSSL SSL_write: SSL_ERROR_SYSCALL, errno 0
+Added comparison and sync context menu options for multiple folder pairs
+Show file include/exclude filter directly in tooltip
+Fixed file not found error when cancelling file up-/download
+Fixed showing cancelled config log status after nothing to sync
+Updated translation files
+
+
FreeFileSync 13.8 [2024-11-04]
------------------------------
Support raw IPv6 server address for (S)FTP
diff --git a/FreeFileSync/Build/Resources/Icons.zip b/FreeFileSync/Build/Resources/Icons.zip
index 22025c9f..54b3e15d 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 f10706f7..01541b62 100644
--- a/FreeFileSync/Build/Resources/Languages.zip
+++ b/FreeFileSync/Build/Resources/Languages.zip
Binary files differ
diff --git a/FreeFileSync/Build/Resources/cacert.pem b/FreeFileSync/Build/Resources/cacert.pem
index f2c24a58..eb11b2fd 100644
--- a/FreeFileSync/Build/Resources/cacert.pem
+++ b/FreeFileSync/Build/Resources/cacert.pem
@@ -1,7 +1,7 @@
##
## Bundle of CA Root Certificates
##
-## Certificate data from Mozilla as of: Tue Sep 24 03:12:04 2024 GMT
+## Certificate data from Mozilla as of: Tue Nov 26 13:58:25 2024 GMT
##
## Find updated versions here: https://curl.se/docs/caextract.html
##
@@ -2602,6 +2602,36 @@ vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----
+GLOBALTRUST 2020
+================
+-----BEGIN CERTIFICATE-----
+MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
+IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
+VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
+BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
+MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
+D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
+VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
+CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
+fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
+A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
+JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
+DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
+clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
+mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
+IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
+VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
+4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
+iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
+8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
+HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
+vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
+oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
+YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
+gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
+-----END CERTIFICATE-----
+
ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
index 7ab659cd..c3e7d1cd 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.cpp
@@ -164,6 +164,36 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
m_staticline212 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer1->Add( m_staticline212, 0, wxEXPAND, 5 );
+ wxBoxSizer* bSizer131;
+ bSizer131 = new wxBoxSizer( wxVERTICAL );
+
+ wxBoxSizer* bSizer14;
+ bSizer14 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_staticText8 = new wxStaticText( m_panelMain, wxID_ANY, _("Idle time (in seconds):"), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText8->Wrap( -1 );
+ bSizer14->Add( m_staticText8, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
+
+ m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 );
+ m_spinCtrlDelay->SetToolTip( _("Idle time between last detected change and execution of command") );
+
+ bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
+
+
+ bSizer131->Add( bSizer14, 0, 0, 5 );
+
+ m_staticText71 = new wxStaticText( m_panelMain, wxID_ANY, _("Ensures folders are not in heavy use when running the command."), wxDefaultPosition, wxDefaultSize, 0 );
+ m_staticText71->Wrap( -1 );
+ m_staticText71->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
+
+ bSizer131->Add( m_staticText71, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+
+
+ bSizer1->Add( bSizer131, 0, wxALL, 10 );
+
+ m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
+ bSizer1->Add( m_staticline211, 0, wxEXPAND, 5 );
+
wxBoxSizer* bSizer141;
bSizer141 = new wxBoxSizer( wxVERTICAL );
@@ -188,24 +218,6 @@ MainDlgGenerated::MainDlgGenerated( wxWindow* parent, wxWindowID id, const wxStr
bSizer1->Add( bSizer141, 0, wxALL|wxEXPAND, 10 );
- m_staticline211 = new wxStaticLine( m_panelMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
- bSizer1->Add( m_staticline211, 0, wxEXPAND, 5 );
-
- wxBoxSizer* bSizer14;
- bSizer14 = new wxBoxSizer( wxHORIZONTAL );
-
- m_staticText8 = new wxStaticText( m_panelMain, wxID_ANY, _("Minimum idle time (in seconds) before running command:"), wxDefaultPosition, wxDefaultSize, 0 );
- m_staticText8->Wrap( -1 );
- bSizer14->Add( m_staticText8, 0, wxALIGN_CENTER_VERTICAL|wxTOP|wxBOTTOM|wxLEFT, 5 );
-
- m_spinCtrlDelay = new wxSpinCtrl( m_panelMain, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 2000000000, 0 );
- m_spinCtrlDelay->SetToolTip( _("Idle time between last detected change and execution of command") );
-
- bSizer14->Add( m_spinCtrlDelay, 0, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxALL, 5 );
-
-
- bSizer1->Add( bSizer14, 0, wxALL|wxEXPAND, 10 );
-
m_panelMain->SetSizer( bSizer1 );
m_panelMain->Layout();
diff --git a/FreeFileSync/Source/RealTimeSync/gui_generated.h b/FreeFileSync/Source/RealTimeSync/gui_generated.h
index b3367031..70830bff 100644
--- a/FreeFileSync/Source/RealTimeSync/gui_generated.h
+++ b/FreeFileSync/Source/RealTimeSync/gui_generated.h
@@ -69,12 +69,13 @@ protected:
wxScrolledWindow* m_scrolledWinFolders;
wxBoxSizer* bSizerFolders;
wxStaticLine* m_staticline212;
+ wxStaticText* m_staticText8;
+ wxSpinCtrl* m_spinCtrlDelay;
+ wxStaticText* m_staticText71;
+ wxStaticLine* m_staticline211;
wxStaticBitmap* m_bitmapConsole;
wxStaticText* m_staticText6;
wxTextCtrl* m_textCtrlCommand;
- wxStaticLine* m_staticline211;
- wxStaticText* m_staticText8;
- wxSpinCtrl* m_spinCtrlDelay;
wxStaticLine* m_staticline5;
zen::BitmapTextButton* m_buttonStart;
diff --git a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
index 7e955915..4a4812e9 100644
--- a/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
+++ b/FreeFileSync/Source/RealTimeSync/main_dlg.cpp
@@ -456,7 +456,7 @@ void MainDialog::insertAddFolder(const std::vector<Zstring>& newFolders, size_t
const size_t visibleRows = std::min(additionalFolderPanels_.size(), MAX_ADD_FOLDERS); //up to MAX_ADD_FOLDERS additional folders shall be shown
m_scrolledWinFolders->SetMinSize({-1, folderHeight * static_cast<int>(visibleRows)});
- m_panelMain->Layout(); //[!] get scrollbars to update correctly
+ m_panelMain->Layout(); //[!] get scrollbars to update correctly
//adapt delete top folder pair button
m_bpButtonRemoveTopFolder->Show(!additionalFolderPanels_.empty());
diff --git a/FreeFileSync/Source/RealTimeSync/monitor.cpp b/FreeFileSync/Source/RealTimeSync/monitor.cpp
index 63b35c91..da1061d3 100644
--- a/FreeFileSync/Source/RealTimeSync/monitor.cpp
+++ b/FreeFileSync/Source/RealTimeSync/monitor.cpp
@@ -118,7 +118,7 @@ std::set<Zstring, LessNativePath> waitForMissingDirs(const std::vector<Zstring>&
DirWatcher::Change waitForChanges(const std::set<Zstring, LessNativePath>& folderPaths, //throw FileError
const std::function<void(bool readyForSync)>& requestUiUpdate, std::chrono::milliseconds cbInterval)
{
- if (folderPaths.empty()) //pathological case, but we have to check else this function will wait endlessly
+ if (folderPaths.empty()) //pathological case, but we have to check or this function waits forever
throw FileError(_("A folder input field is empty.")); //should have been checked by caller!
std::vector<std::pair<Zstring, std::unique_ptr<DirWatcher>>> watches;
diff --git a/FreeFileSync/Source/afs/abstract.h b/FreeFileSync/Source/afs/abstract.h
index b9292d33..94c14ead 100644
--- a/FreeFileSync/Source/afs/abstract.h
+++ b/FreeFileSync/Source/afs/abstract.h
@@ -170,18 +170,18 @@ struct AbstractFileSystem //THREAD-SAFETY: "const" member functions must model t
virtual FinalizeResult finalize(const zen::IoCallback& notifyUnbufferedIO /*throw X*/) = 0; //throw FileError, X
};
- struct OutputStream //call finalize when done!
+ struct OutputStream
{
OutputStream(std::unique_ptr<OutputStreamImpl>&& outStream, const AbstractPath& filePath, std::optional<uint64_t> streamSize);
~OutputStream();
size_t getBlockSize() { return outStream_->getBlockSize(); } //throw FileError
size_t tryWrite(const void* buffer, size_t bytesToWrite, const zen::IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X may return short!
FinalizeResult finalize(const zen::IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X
+ //call finalize when done!() when done, or (incomplete) file will be automatically deleted
private:
std::unique_ptr<OutputStreamImpl> outStream_; //bound!
const AbstractPath filePath_;
- bool finalizeSucceeded_ = false;
const std::optional<uint64_t> bytesExpected_;
uint64_t bytesWrittenTotal_ = 0;
};
@@ -479,17 +479,6 @@ AbstractFileSystem::OutputStream::OutputStream(std::unique_ptr<OutputStreamImpl>
inline
AbstractFileSystem::OutputStream::~OutputStream()
{
- //we delete the file on errors: => file should not have existed prior to creating OutputStream instance!!
- outStream_.reset(); //close file handle *before* remove!
-
- if (!finalizeSucceeded_) //transactional output stream! => clean up!
- //- needed for Google Drive: e.g. user might cancel during OutputStreamImpl::finalize(), just after file was written transactionally
- //- also for Native: setFileTime() may fail *after* FileOutput::finalize()
- try { AbstractFileSystem::removeFilePlain(filePath_); /*throw FileError*/ }
- catch (const zen::FileError& e) { zen::logExtraError(e.toString()); }
-
- warn_static("we should not log if not existing anymore!?: ERROR_FILE_NOT_FOUND: ddddddddddd [DeleteFile]")
- //solution: integrate cleanup into ~OutputStreamImpl() including appropriate loggin!
}
@@ -514,7 +503,6 @@ AbstractFileSystem::FinalizeResult AbstractFileSystem::OutputStream::finalize(co
_("Expected:") + L' ' + formatNumber(*bytesExpected_));
const FinalizeResult result = outStream_->finalize(notifyUnbufferedIO); //throw FileError, X
- finalizeSucceeded_ = true;
return result;
}
diff --git a/FreeFileSync/Source/afs/ftp.cpp b/FreeFileSync/Source/afs/ftp.cpp
index f77e3c8b..b9bbe24b 100644
--- a/FreeFileSync/Source/afs/ftp.cpp
+++ b/FreeFileSync/Source/afs/ftp.cpp
@@ -182,7 +182,7 @@ std::vector<std::string_view> splitFtpResponse(const std::string& buf)
{
std::vector<std::string_view> lines;
- split2(buf, [](char c) { return isLineBreak(c) || c == '\0'; }, //is 0-char check even needed?
+ split2(buf, [](const char c) { return isLineBreak(c) || c == '\0'; }, //is 0-char check even needed?
[&lines](const std::string_view block)
{
if (!block.empty()) //consider Windows' <CR><LF>
@@ -1522,13 +1522,13 @@ private:
{
FtpLineParser parser(rawLine);
- const std::string_view typeTag = parser.readRange(1, [](char c) //throw SysError
+ const std::string_view typeTag = parser.readRange(1, [](const char c) //throw SysError
{
return c == '-' || c == 'b' || c == 'c' || c == 'd' || c == 'l' || c == 'p' || c == 's';
});
//------------------------------------------------------------------------------------
//permissions
- parser.readRange(9, [](char c) //throw SysError
+ parser.readRange(9, [](const char c) //throw SysError
{
return c == '-' || c == 'r' || c == 'w' || c == 'x' || c == 's' || c == 'S' || c == 't' || c == 'T';
});
@@ -1563,7 +1563,7 @@ private:
if (day < 1 || day > 31)
throw SysError(L"Failed to parse day of month.");
//------------------------------------------------------------------------------------
- const std::string_view timeOrYear = parser.readRange([](char c) { return c == ':' || isDigit(c); }); //throw SysError
+ const std::string_view timeOrYear = parser.readRange([](const char c) { return c == ':' || isDigit(c); }); //throw SysError
parser.readRange(&isWhiteSpace<char>); //throw SysError
TimeComp timeComp;
@@ -1677,9 +1677,9 @@ private:
FtpLineParser parser(line);
const int month = stringTo<int>(parser.readRange(2, &isDigit<char>)); //throw SysError
- parser.readRange(1, [](char c) { return c == '-' || c == '/'; }); //throw SysError
+ parser.readRange(1, [](const char c) { return c == '-' || c == '/'; }); //throw SysError
const int day = stringTo<int>(parser.readRange(2, &isDigit<char>)); //throw SysError
- parser.readRange(1, [](char c) { return c == '-' || c == '/'; }); //throw SysError
+ parser.readRange(1, [](const char c) { return c == '-' || c == '/'; }); //throw SysError
const std::string_view yearString = parser.readRange(&isDigit<char>); //throw SysError
parser.readRange(&isWhiteSpace<char>); //throw SysError
@@ -1699,11 +1699,11 @@ private:
throw SysError(L"Failed to parse modification time.");
//------------------------------------------------------------------------------------
int hour = stringTo<int>(parser.readRange(2, &isDigit<char>)); //throw SysError
- parser.readRange(1, [](char c) { return c == ':'; }); //throw SysError
+ parser.readRange(1, [](const char c) { return c == ':'; }); //throw SysError
const int minute = stringTo<int>(parser.readRange(2, &isDigit<char>)); //throw SysError
if (!isWhiteSpace(parser.peekNextChar()))
{
- const std::string_view period = parser.readRange(2, [](char c) { return c == 'A' || c == 'P' || c == 'M'; }); //throw SysError
+ const std::string_view period = parser.readRange(2, [](const char c) { return c == 'A' || c == 'P' || c == 'M'; }); //throw SysError
if (period == "PM")
{
if (0 <= hour && hour < 12)
@@ -2087,8 +2087,23 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl
~OutputStreamFtp()
{
- if (asyncStreamOut_) //finalize() was not called (successfully)
+ if (asyncStreamOut_) //=> cleanup non-finalized output file
+ {
asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadStopRequest()));
+ worker_.join();
+
+ try //see removeFilePlain()
+ {
+ accessFtpSession(login_, [&](FtpSession& session) //throw SysError
+ {
+ session.runSingleFtpCommand("DELE " + session.getServerPathInternal(filePath_), true /*requestUtf8*/); //throw SysError, SysErrorFtpProtocol
+ });
+ }
+ catch (const SysError& e)
+ {
+ logExtraError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getCurlDisplayPath(login_, filePath_))) + L"\n\n" + e.toString());
+ }
+ }
}
size_t getBlockSize() override { return FTP_BLOCK_SIZE_UPLOAD; } //throw (FileError)
@@ -2115,7 +2130,7 @@ struct OutputStreamFtp : public AFS::OutputStreamImpl
futUploadDone_.get(); //throw FileError
//asyncStreamOut_->checkReadErrors(); //throw FileError -> not needed after *successful* upload
- asyncStreamOut_.reset(); //do NOT reset on error, so that ~OutputStreamFtp() will request worker thread to stop
+ asyncStreamOut_.reset(); //output finalized => no more exceptions from here on!
//--------------------------------------------------------------------
AFS::FinalizeResult result;
diff --git a/FreeFileSync/Source/afs/gdrive.cpp b/FreeFileSync/Source/afs/gdrive.cpp
index 6652b644..6ba67f77 100644
--- a/FreeFileSync/Source/afs/gdrive.cpp
+++ b/FreeFileSync/Source/afs/gdrive.cpp
@@ -2034,7 +2034,7 @@ public:
{
FileStateDelta() {}
private:
- FileStateDelta(const std::shared_ptr<const ItemIdDelta>& cids) : changedIds(cids) {}
+ explicit FileStateDelta(const std::shared_ptr<const ItemIdDelta>& cids) : changedIds(cids) {}
friend class GdriveFileState;
std::shared_ptr<const ItemIdDelta> changedIds; //lifetime is managed by caller; access *only* by GdriveFileState!
};
@@ -3178,7 +3178,8 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
OutputStreamGdrive(const GdrivePath& gdrivePath,
std::optional<uint64_t> /*streamSize*/,
std::optional<time_t> modTime,
- std::unique_ptr<PathAccessLock>&& pal) //throw SysError
+ std::unique_ptr<PathAccessLock>&& pal) : //throw SysError
+ gdrivePath_(gdrivePath)
{
std::promise<AFS::FingerPrint> promFilePrint;
futFilePrint_ = promFilePrint.get_future();
@@ -3256,8 +3257,36 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
~OutputStreamGdrive()
{
- if (asyncStreamOut_) //finalize() was not called (successfully)
+ if (asyncStreamOut_) //=> cleanup non-finalized output file
+ {
asyncStreamOut_->setWriteError(std::make_exception_ptr(ThreadStopRequest()));
+ worker_.join();
+
+ try //see removeFilePlain()
+ {
+ std::optional<std::string> itemId;
+ const GdrivePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
+ {
+ const GdriveFileState::PathStatus ps = fileState.getPathStatus(gdrivePath_.itemPath, false /*followLeafShortcut*/); //throw SysError
+ if (ps.relPath.empty())
+ itemId = ps.existingItemId;
+ });
+ if (itemId)
+ {
+ gdriveDeleteItem(*itemId, aai.access); //throw SysError
+
+ //buffer new file state ASAP (don't wait GDRIVE_SYNC_INTERVAL)
+ accessGlobalFileState(gdrivePath_.gdriveLogin, [&](GdriveFileStateAtLocation& fileState) //throw SysError
+ {
+ fileState.all().notifyItemDeleted(aai.stateDelta, *itemId);
+ });
+ }
+ }
+ catch (const SysError& e)
+ {
+ logExtraError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getGdriveDisplayPath(gdrivePath_))) + L"\n\n" + e.toString());
+ }
+ }
}
size_t getBlockSize() override { return GDRIVE_BLOCK_SIZE_UPLOAD; } //throw (FileError)
@@ -3285,7 +3314,7 @@ struct OutputStreamGdrive : public AFS::OutputStreamImpl
result.filePrint = futFilePrint_.get(); //throw FileError
//asyncStreamOut_->checkReadErrors(); //throw FileError -> not needed after *successful* upload
- asyncStreamOut_.reset(); //do NOT reset on error, so that ~OutputStreamGdrive() will request worker thread to stop
+ asyncStreamOut_.reset(); //output finalized => no more exceptions from here on!
//--------------------------------------------------------------------
//result.errorModTime -> already (successfully) set during file creation
@@ -3300,6 +3329,7 @@ private:
if (notifyUnbufferedIO) notifyUnbufferedIO(bytesDelta); //throw X
}
+ const GdrivePath gdrivePath_;
int64_t totalBytesReported_ = 0;
std::shared_ptr<AsyncStreamBuffer> asyncStreamOut_ = std::make_shared<AsyncStreamBuffer>(GDRIVE_STREAM_BUFFER_SIZE);
InterruptibleThread worker_;
diff --git a/FreeFileSync/Source/afs/native.cpp b/FreeFileSync/Source/afs/native.cpp
index fd6fa928..592a9e4b 100644
--- a/FreeFileSync/Source/afs/native.cpp
+++ b/FreeFileSync/Source/afs/native.cpp
@@ -379,6 +379,9 @@ struct OutputStreamNative : public AFS::OutputStreamImpl
result.filePrint = getNativeFileInfo(fileOut_).filePrint; //throw FileError
fileOut_.close(); //throw FileError
+ //output finalized => no more exceptions from here on!
+ //--------------------------------------------------------------------
+
/* is setting modtime after closing the file handle a pessimization?
no, needed for functional correctness, see file_access.cpp::copyNewFile() for macOS/Linux
even required on Windows: https://freefilesync.org/forum/viewtopic.php?t=10781 */
diff --git a/FreeFileSync/Source/afs/sftp.cpp b/FreeFileSync/Source/afs/sftp.cpp
index 61e3ae3b..37d8f77a 100644
--- a/FreeFileSync/Source/afs/sftp.cpp
+++ b/FreeFileSync/Source/afs/sftp.cpp
@@ -381,7 +381,7 @@ public:
return L"OpenSSH public key"; //OpenSSH SSH-2 public key
if (std::count(pkStream.begin(), pkStream.end(), ' ') == 2 &&
- /**/std::all_of(pkStream.begin(), pkStream.end(), [](char c) { return isDigit(c) || c == ' '; }))
+ /**/std::all_of(pkStream.begin(), pkStream.end(), [](const char c) { return isDigit(c) || c == ' '; }))
return L"SSH-1 public key";
//"-----BEGIN PRIVATE KEY-----" => OpenSSH SSH-2 private key (PKCS#8 PrivateKeyInfo) => should work
@@ -1385,8 +1385,8 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
OutputStreamSftp(const SftpLogin& login, //throw FileError
const AfsPath& filePath,
std::optional<time_t> modTime) :
+ login_(login),
filePath_(filePath),
- displayPath_(getSftpDisplayPath(login, filePath)),
modTime_(modTime)
{
try
@@ -1404,7 +1404,7 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
return LIBSSH2_ERROR_NONE;
});
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getSftpDisplayPath(login_, filePath_))), e.toString()); }
//NOTE: fileHandle_ still unowned until end of constructor!!!
@@ -1413,12 +1413,24 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
~OutputStreamSftp()
{
- if (fileHandle_)
- try
+ if (fileHandle_) //=> cleanup non-finalized output file
+ {
+ if (!closeFailed_) //otherwise there's no much point in calling libssh2_sftp_close() a second time => let it leak!?
+ try { close(); /*throw FileError*/ }
+ catch (const FileError& e) { logExtraError(e.toString()); }
+
+ session_.reset(); //reset before file deletion to potentially get new session if !SshSession::isHealthy()
+
+ try //see removeFilePlain()
{
- close(); //throw FileError
+ runSftpCommand(login_, "libssh2_sftp_unlink", //throw SysError, SysErrorSftpProtocol
+ [&](const SshSession::Details& sd) { return ::libssh2_sftp_unlink(sd.sftpChannel, getLibssh2Path(filePath_)); }); //noexcept!
}
- catch (const FileError& e) { logExtraError(e.toString()); }
+ catch (const SysError& e)
+ {
+ logExtraError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getSftpDisplayPath(login_, filePath_))) + L"\n\n" + e.toString());
+ }
+ }
}
size_t getBlockSize() override { return SFTP_OPTIMAL_BLOCK_SIZE_WRITE; } //throw (FileError)
@@ -1445,7 +1457,7 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
ASSERT_SYSERROR(makeUnsigned(bytesWritten) <= bytesToWrite); //better safe than sorry
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getSftpDisplayPath(login_, filePath_))), e.toString()); }
if (notifyUnbufferedIO) notifyUnbufferedIO(bytesWritten); //throw X!
@@ -1454,10 +1466,9 @@ struct OutputStreamSftp : public AFS::OutputStreamImpl
AFS::FinalizeResult finalize(const IoCallback& notifyUnbufferedIO /*throw X*/) override //throw FileError, X
{
- //~OutputStreamSftp() would call this one, too, but we want to propagate errors if any:
close(); //throw FileError
-
- //it seems libssh2_sftp_fsetstat() triggers bugs on synology server => set mtime by path! https://freefilesync.org/forum/viewtopic.php?t=1281
+ //output finalized => no more exceptions from here on!
+ //--------------------------------------------------------------------
AFS::FinalizeResult result;
//result.filePrint = ... -> not supported by SFTP
@@ -1478,13 +1489,18 @@ private:
if (!fileHandle_)
throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
- ZEN_ON_SCOPE_EXIT(fileHandle_ = nullptr); //reset on error, too! there's no point in, calling libssh2_sftp_close() a second time in ~OutputStreamSftp()
try
{
session_->executeBlocking("libssh2_sftp_close", //throw SysError, SysErrorSftpProtocol
[&](const SshSession::Details& sd) { return ::libssh2_sftp_close(fileHandle_); }); //noexcept!
+
+ fileHandle_ = nullptr;
+ }
+ catch (const SysError& e)
+ {
+ closeFailed_ = true;
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getSftpDisplayPath(login_, filePath_))), e.toString());
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(displayPath_)), e.toString()); }
}
void setModTimeIfAvailable() const //throw FileError, follows symlinks
@@ -1497,19 +1513,21 @@ private:
attribNew.mtime = static_cast<decltype(attribNew.mtime)>(*modTime_); //32-bit target! loss of data!
attribNew.atime = static_cast<decltype(attribNew.atime)>(::time(nullptr)); //
+ //it seems libssh2_sftp_fsetstat() triggers bugs on synology server => set mtime by path! https://freefilesync.org/forum/viewtopic.php?t=1281
try
{
session_->executeBlocking("libssh2_sftp_setstat", //throw SysError, SysErrorSftpProtocol
[&](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()); }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(getSftpDisplayPath(login_, filePath_))), e.toString()); }
}
}
+ const SftpLogin login_;
const AfsPath filePath_;
- const std::wstring displayPath_;
- LIBSSH2_SFTP_HANDLE* fileHandle_ = nullptr;
const std::optional<time_t> modTime_;
+ LIBSSH2_SFTP_HANDLE* fileHandle_ = nullptr;
+ bool closeFailed_ = false;
std::shared_ptr<SftpSessionManager::SshSessionShared> session_;
};
diff --git a/FreeFileSync/Source/application.cpp b/FreeFileSync/Source/application.cpp
index fc9032f3..0f82caa6 100644
--- a/FreeFileSync/Source/application.cpp
+++ b/FreeFileSync/Source/application.cpp
@@ -524,7 +524,7 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
//all settings have been read successfully...
- /* regular check for program updates -> disabled for batch
+ /* regular check for software 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/help/238425/info-wininet-not-supported-for-use-in-services */
@@ -694,13 +694,13 @@ void Application::runBatchMode(const Zstring& globalConfigFilePath, const XmlBat
for (ConfigFileItem& cfi : globalCfg.mainDlg.config.fileHistory)
if (equalNativePath(cfi.cfgFilePath, cfgFilePath))
{
- assert(!AFS::isNullPath(logFilePath));
assert(r.summary.startTime == syncStartTime);
+ assert(!AFS::isNullPath(logFilePath));
cfi.lastRunStats =
{
- logFilePath,
std::chrono::system_clock::to_time_t(r.summary.startTime),
+ logFilePath,
r.summary.result,
r.summary.statsProcessed.items,
r.summary.statsProcessed.bytes,
diff --git a/FreeFileSync/Source/base/db_file.cpp b/FreeFileSync/Source/base/db_file.cpp
index eb62ccb2..e75f7cdb 100644
--- a/FreeFileSync/Source/base/db_file.cpp
+++ b/FreeFileSync/Source/base/db_file.cpp
@@ -87,17 +87,17 @@ void saveStreams(const DbStreams& streamList, const AbstractPath& dbPath, const
//------------------------------------------------------------------------------------------------------------------------
//already existing: undefined behavior! (e.g. fail/overwrite/auto-rename)
- const std::unique_ptr<AFS::OutputStream> fileStreamOut = AFS::getOutputStream(dbPath,
+ const std::unique_ptr<AFS::OutputStream> byteStreamOut = AFS::getOutputStream(dbPath,
memStreamOut.ref().size(),
std::nullopt /*modTime*/); //throw FileError
unbufferedSave(memStreamOut.ref(), [&](const void* buffer, size_t bytesToWrite)
{
- return fileStreamOut->tryWrite(buffer, bytesToWrite, notifyUnbufferedIO); //throw FileError, X
+ return byteStreamOut->tryWrite(buffer, bytesToWrite, notifyUnbufferedIO); //throw FileError, X
},
- fileStreamOut->getBlockSize()); //throw FileError, X
+ byteStreamOut->getBlockSize()); //throw FileError, X
- fileStreamOut->finalize(notifyUnbufferedIO); //throw FileError, X
+ byteStreamOut->finalize(notifyUnbufferedIO); //throw FileError, X
}
diff --git a/FreeFileSync/Source/base/path_filter.cpp b/FreeFileSync/Source/base/path_filter.cpp
index 55cc0cf5..16a206ea 100644
--- a/FreeFileSync/Source/base/path_filter.cpp
+++ b/FreeFileSync/Source/base/path_filter.cpp
@@ -293,7 +293,7 @@ bool NameFilter::passDirFilter(const Zstring& relDirPath, bool* childItemMightMa
bool NameFilter::isNull(const Zstring& includePhrase, const Zstring& excludePhrase)
{
- return trimCpy(includePhrase) == Zstr("*") &&
+ return trimCpy(includePhrase) == Zstr("*") && //harmonize with ui/folder_pair.cpp tooltip
trimCpy(excludePhrase).empty();
//return NameFilter(includePhrase, excludePhrase).isNull(); -> very expensive for huge lists
}
diff --git a/FreeFileSync/Source/base/synchronization.cpp b/FreeFileSync/Source/base/synchronization.cpp
index f632faa2..b241e81a 100644
--- a/FreeFileSync/Source/base/synchronization.cpp
+++ b/FreeFileSync/Source/base/synchronization.cpp
@@ -2834,7 +2834,7 @@ break2:
}
}
if (!msg.empty())
- callback.reportWarning(_("The versioning folder is part of the synchronization.") +
+ callback.reportWarning(_("The versioning folder must not be part of the synchronization.") +
(shouldExclude ? L' ' + _("The folder should be excluded via filter.") : L"") +
msg, warnings.warnVersioningFolderPartOfSync); //throw X
}
diff --git a/FreeFileSync/Source/localization.cpp b/FreeFileSync/Source/localization.cpp
index 22eab503..c6490954 100644
--- a/FreeFileSync/Source/localization.cpp
+++ b/FreeFileSync/Source/localization.cpp
@@ -11,7 +11,6 @@
#include <wx/zipstrm.h>
#include <wx/mstream.h>
#include <wx/uilocale.h>
-#include "parse_plural.h"
#include "parse_lng.h"
using namespace zen;
@@ -91,36 +90,49 @@ FFSTranslation::FFSTranslation(const std::string& lngStream, bool haveRtlLayout)
std::vector<TranslationInfo> loadTranslations(const Zstring& zipPath) //throw FileError
{
- std::vector<std::pair<Zstring /*file name*/, std::string /*byte stream*/>> streams;
-
- try //to load from ZIP first:
+ std::vector<std::pair<Zstring /*file path*/, std::string /*byte stream*/>> streams;
+ [&]
{
- const std::string rawStream = getFileContent(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError
- wxMemoryInputStream memStream(rawStream.c_str(), rawStream.size()); //does not take ownership
- wxZipInputStream zipStream(memStream, wxConvUTF8);
+ std::string rawStream;
+ try //to load from ZIP first:
+ {
+ rawStream = getFileContent(zipPath, nullptr /*notifyUnbufferedIO*/); //throw FileError
+ }
+ catch (FileError&) //fall back to folder: dev build (only!?)
+ {
+ const Zstring fallbackFolder = beforeLast(zipPath, Zstr(".zip"), IfNotFoundReturn::none);
+ if (!itemExists(fallbackFolder)) //throw FileError
+ throw;
+
+ traverseFolder(fallbackFolder, [&](const FileInfo& fi)
+ {
+ if (endsWith(fi.fullPath, Zstr(".lng")))
+ {
+ std::string stream = getFileContent(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError
+ streams.emplace_back(fi.fullPath, std::move(stream));
+ }
+ }, nullptr, nullptr); //throw FileError
+ return;
+ }
+ //-------------------------------------------------------------
+
+ wxMemoryInputStream byteStream(rawStream.c_str(), rawStream.size()); //does not take ownership
+ wxZipInputStream zipStream(byteStream, wxConvUTF8);
while (const auto& entry = std::unique_ptr<wxZipEntry>(zipStream.GetNextEntry())) //take ownership!
+ {
+ if (entry->IsDir()) //e.g. translators accidentally ZIPing "Languages" directory
+ throw FileError(replaceCpy(replaceCpy<std::wstring>(L"ZIP file %x contains unexpected sub directory %y.",
+ L"%x", fmtPath(zipPath)),
+ L"%y", fmtPath(utfTo<std::wstring>(entry->GetName()))));
+
if (std::string stream(entry->GetSize(), '\0');
zipStream.ReadAll(stream.data(), stream.size()))
- streams.emplace_back(utfTo<Zstring>(entry->GetName()), std::move(stream));
+ streams.emplace_back(zipPath + Zstr(':') + utfTo<Zstring>(entry->GetName()), std::move(stream));
else
assert(false);
- }
- catch (FileError&) //fall back to folder: dev build (only!?)
- {
- const Zstring fallbackFolder = beforeLast(zipPath, Zstr(".zip"), IfNotFoundReturn::none);
- if (!itemExists(fallbackFolder)) //throw FileError
- throw;
-
- traverseFolder(fallbackFolder, [&](const FileInfo& fi)
- {
- if (endsWith(fi.fullPath, Zstr(".lng")))
- {
- std::string stream = getFileContent(fi.fullPath, nullptr /*notifyUnbufferedIO*/); //throw FileError
- streams.emplace_back(fi.itemName, std::move(stream));
- }
- }, nullptr, nullptr); //throw FileError
- }
+ }
+ }();
//--------------------------------------------------------------------
std::vector<TranslationInfo> translations
@@ -137,7 +149,7 @@ std::vector<TranslationInfo> loadTranslations(const Zstring& zipPath) //throw Fi
}
};
- for (/*const*/ auto& [fileName, stream] : streams)
+ for (/*const*/ auto& [filePath, stream] : streams)
try
{
const lng::TransHeader lngHeader = lng::parseHeader(stream); //throw ParsingError
@@ -156,14 +168,14 @@ std::vector<TranslationInfo> loadTranslations(const Zstring& zipPath) //throw Fi
.languageName = utfTo<std::wstring>(lngHeader.languageName),
.translatorName = utfTo<std::wstring>(lngHeader.translatorName),
.languageFlag = lngHeader.flagFile,
- .lngFileName = fileName,
+ .lngFileName = filePath,
.lngStream = std::move(stream),
});
}
catch (const lng::ParsingError& e)
{
throw FileError(replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."),
- L"%x", fmtPath(fileName)),
+ L"%x", fmtPath(filePath)),
L"%y", formatNumber(e.row + 1)),
L"%z", formatNumber(e.col + 1))
+ L"\n\n" + e.msg);
diff --git a/FreeFileSync/Source/parse_lng.h b/FreeFileSync/Source/parse_lng.h
index 2d3dae91..01582e17 100644
--- a/FreeFileSync/Source/parse_lng.h
+++ b/FreeFileSync/Source/parse_lng.h
@@ -39,11 +39,11 @@ struct ParsingError
size_t row = 0; //starting with 0
size_t col = 0; //
};
-TransHeader parseHeader(const std::string& fileStream); //throw ParsingError
-void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut); //throw ParsingError
+TransHeader parseHeader(const std::string& byteStream); //throw ParsingError
+void parseLng(const std::string& byteStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut); //throw ParsingError
class TranslationUnorderedList; //unordered list of unique translation items
-std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header);
+std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header, bool untranslatedToTop);
@@ -147,23 +147,8 @@ private:
enum class TokenType
{
- //header information
headerBegin,
headerEnd,
- langNameBegin,
- langNameEnd,
- transNameBegin,
- transNameEnd,
- localeBegin,
- localeEnd,
- flagFileBegin,
- flagFileEnd,
- pluralCountBegin,
- pluralCountEnd,
- pluralDefBegin,
- pluralDefEnd,
-
- //item level
srcBegin,
srcEnd,
trgBegin,
@@ -204,23 +189,8 @@ public:
private:
const TokenMap tokens_ =
{
- //header details
- {TokenType::headerBegin, "<header>"},
- {TokenType::headerEnd, "</header>"},
- {TokenType::langNameBegin, "<language>"},
- {TokenType::langNameEnd, "</language>"},
- {TokenType::transNameBegin, "<translator>"},
- {TokenType::transNameEnd, "</translator>"},
- {TokenType::localeBegin, "<locale>"},
- {TokenType::localeEnd, "</locale>"},
- {TokenType::flagFileBegin, "<image>"},
- {TokenType::flagFileEnd, "</image>"},
- {TokenType::pluralCountBegin, "<plural_count>"},
- {TokenType::pluralCountEnd, "</plural_count>"},
- {TokenType::pluralDefBegin, "<plural_definition>"},
- {TokenType::pluralDefEnd, "</plural_definition>"},
-
- //item level
+ {TokenType::headerBegin, "<header>"},
+ {TokenType::headerEnd, "</header>"},
{TokenType::srcBegin, "<source>"},
{TokenType::srcEnd, "</source>"},
{TokenType::trgBegin, "<target>"},
@@ -234,7 +204,7 @@ private:
class Scanner
{
public:
- Scanner(const std::string& byteStream) : stream_(byteStream), pos_(stream_.begin())
+ explicit Scanner(const std::string& byteStream) : stream_(byteStream), pos_(stream_.begin())
{
if (zen::startsWith(stream_, zen::BYTE_ORDER_MARK_UTF8))
pos_ += zen::strLength(zen::BYTE_ORDER_MARK_UTF8);
@@ -327,11 +297,11 @@ private:
class LngParser
{
public:
- explicit LngParser(const std::string& fileStream) : scn_(fileStream), tk_(scn_.getNextToken()) {}
+ explicit LngParser(const std::string& byteStream) : scn_(byteStream), tk_(scn_.getNextToken()) {}
- void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header)
+ void parse(TranslationMap& out, TranslationPluralMap& pluralOut, TransHeader& header) //throw ParsingError
{
- parseHeader(header);
+ parseHeader(header); //throw ParsingError
try
{
@@ -339,7 +309,7 @@ public:
//items
while (token().type != TokenType::end)
- parseRegular(out, pluralOut, pi);
+ parseRegular(out, pluralOut, pi); //throw ParsingError
}
catch (const plural::InvalidPluralForm&)
{
@@ -347,50 +317,50 @@ public:
}
}
- void parseHeader(TransHeader& header)
+ void parseHeader(TransHeader& header) //throw ParsingError
{
- consumeToken(TokenType::headerBegin); //throw ParsingError
+ using namespace zen;
- consumeToken(TokenType::langNameBegin); //throw ParsingError
- header.languageName = token().text;
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::langNameEnd); //
+ consumeToken(TokenType::headerBegin); //throw ParsingError
- consumeToken(TokenType::transNameBegin); //throw ParsingError
- header.translatorName = token().text;
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::transNameEnd); //
+ const std::string headerRaw = token().text;
+ consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::localeBegin); //throw ParsingError
- header.locale = token().text;
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::localeEnd); //
+ std::unordered_map<std::string_view, std::string_view> items;
- consumeToken(TokenType::flagFileBegin); //throw ParsingError
- header.flagFile = token().text;
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::flagFileEnd); //
+ split2(headerRaw, [](const char c) { return isLineBreak(c); },
+ [&items](const std::string_view block)
+ {
+ const std::string_view name = trimCpy(beforeFirst(block, ':', IfNotFoundReturn::none));
+ if (!name.empty())
+ items.emplace(name, trimCpy(afterFirst(block, ':', IfNotFoundReturn::none)));
+ });
- consumeToken(TokenType::pluralCountBegin); //throw ParsingError
- header.pluralCount = zen::stringTo<int>(token().text);
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::pluralCountEnd); //
+ auto getValue = [&](const std::string_view name)
+ {
+ auto it = items.find(name);
+ if (it == items.end())
+ throw ParsingError({replaceCpy<std::wstring>(L"Cannot find header item \"%x:\"", L"%x", utfTo<std::wstring>(name)), scn_.posRow(), scn_.posCol()});
+ return it->second;
+ };
- consumeToken(TokenType::pluralDefBegin); //throw ParsingError
- header.pluralDefinition = token().text;
- consumeToken(TokenType::text); //throw ParsingError
- consumeToken(TokenType::pluralDefEnd); //
+ header.languageName = getValue("language"); //throw ParsingError
+ header.locale = getValue("locale"); //throw ParsingError
+ header.flagFile = getValue("image"); //throw ParsingError
+ header.pluralCount = stringTo<int>(getValue("plural_count")); //throw ParsingError
+ header.pluralDefinition = getValue("plural_definition"); //throw ParsingError
+ header.translatorName = getValue("translator"); //throw ParsingError
consumeToken(TokenType::headerEnd); //throw ParsingError
}
private:
- void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo)
+ void parseRegular(TranslationMap& out, TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) //throw ParsingError
{
consumeToken(TokenType::srcBegin); //throw ParsingError
if (token().type == TokenType::pluralBegin)
- return parsePlural(pluralOut, pluralInfo);
+ return parsePlural(pluralOut, pluralInfo); //throw ParsingError
std::string original = token().text;
consumeToken(TokenType::text); //throw ParsingError
@@ -409,7 +379,7 @@ private:
out.emplace(original, std::move(translation));
}
- void parsePlural(TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo)
+ void parsePlural(TranslationPluralMap& pluralOut, const plural::PluralFormInfo& pluralInfo) //throw ParsingError
{
//TokenType::srcBegin already consumed
@@ -576,7 +546,7 @@ private:
contains(original.second, placeholder))
for (const std::string& str : allTexts)
if (!contains(str, placeholder))
- throw ParsingError({zen::replaceCpy<std::wstring>(L"Placeholder %x missing in text", L"%x", zen::utfTo<std::wstring>(placeholder)), scn_.posRow(), scn_.posCol()});
+ throw ParsingError({replaceCpy<std::wstring>(L"Placeholder %x missing in text", L"%x", utfTo<std::wstring>(placeholder)), scn_.posRow(), scn_.posCol()});
};
checkSecondaryPlaceholder("%y");
checkSecondaryPlaceholder("%z");
@@ -667,8 +637,8 @@ private:
endsWith(s, "\xe3\x80\x82")) //chinese period
&&
(!endsWith(s, "..") &&
- !endsWith(s, "\xe0\xa5\xa4\xe0\xa5\xa4") && //hindi period
- !endsWith(s, "\xe3\x80\x82\xe3\x80\x82")); //chinese period
+ !endsWith(s, "\xe0\xa5\xa4" "\xe0\xa5\xa4") && //hindi period
+ !endsWith(s, "\xe3\x80\x82" "\xe3\x80\x82")); //chinese period
}
@@ -694,20 +664,20 @@ private:
inline
-void parseLng(const std::string& fileStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut) //throw ParsingError
+void parseLng(const std::string& byteStream, TransHeader& header, TranslationMap& out, TranslationPluralMap& pluralOut) //throw ParsingError
{
out.clear();
pluralOut.clear();
- LngParser(fileStream).parse(out, pluralOut, header);
+ LngParser(byteStream).parse(out, pluralOut, header); //throw ParsingError
}
inline
-TransHeader parseHeader(const std::string& fileStream) //throw ParsingError
+TransHeader parseHeader(const std::string& byteStream) //throw ParsingError
{
TransHeader header;
- LngParser(fileStream).parseHeader(header);
+ LngParser(byteStream).parseHeader(header); //throw ParsingError
return header;
}
@@ -730,17 +700,19 @@ void formatMultiLineText(std::string& text)
inline
std::string generateLng(const TranslationUnorderedList& in, const TransHeader& header, bool untranslatedToTop)
{
+ using namespace zen;
+
const KnownTokens tokens; //no need for static non-POD!
std::string headerLines;
headerLines += tokens.text(TokenType::headerBegin) + '\n';
- headerLines += '\t' + tokens.text(TokenType::langNameBegin) + header.languageName + tokens.text(TokenType::langNameEnd) + '\n';
- headerLines += '\t' + tokens.text(TokenType::transNameBegin) + header.translatorName + tokens.text(TokenType::transNameEnd) + '\n';
- headerLines += '\t' + tokens.text(TokenType::localeBegin) + header.locale + tokens.text(TokenType::localeEnd) + '\n';
- headerLines += '\t' + tokens.text(TokenType::flagFileBegin) + header.flagFile + tokens.text(TokenType::flagFileEnd) + '\n';
- headerLines += '\t' + tokens.text(TokenType::pluralCountBegin) + zen::numberTo<std::string>(header.pluralCount) + tokens.text(TokenType::pluralCountEnd) + '\n';
- headerLines += '\t' + tokens.text(TokenType::pluralDefBegin) + header.pluralDefinition + tokens.text(TokenType::pluralDefEnd) + '\n';
+ headerLines += "\t" "language: " + header.languageName + '\n';
+ headerLines += "\t" "locale: " + header.locale + '\n';
+ headerLines += "\t" "image: " + header.flagFile + '\n';
+ headerLines += "\t" "plural_count: " + numberTo<std::string>(header.pluralCount) + '\n';
+ headerLines += "\t" "plural_definition: " + header.pluralDefinition + '\n';
+ headerLines += "\t" "translator: " + header.translatorName + '\n';
headerLines += tokens.text(TokenType::headerEnd) + "\n\n";
@@ -789,8 +761,8 @@ std::string generateLng(const TranslationUnorderedList& in, const TransHeader& h
});
std::string output = headerLines + topLines + mainLines;
- assert(!zen::contains(output, "\r\n") && !zen::contains(output, "\r"));
- return zen::replaceCpy(output, '\n', "\r\n"); //back to Windows line endings
+ assert(!contains(output, "\r\n") && !contains(output, "\r"));
+ return replaceCpy(output, '\n', "\r\n"); //back to Windows line endings
}
}
diff --git a/FreeFileSync/Source/ui/cfg_grid.cpp b/FreeFileSync/Source/ui/cfg_grid.cpp
index ff9ae7d6..2d5f8665 100644
--- a/FreeFileSync/Source/ui/cfg_grid.cpp
+++ b/FreeFileSync/Source/ui/cfg_grid.cpp
@@ -268,13 +268,13 @@ void ConfigView::sortListViewImpl()
if (lhs->second.isLastRunCfg != rhs->second.isLastRunCfg)
return lhs->second.isLastRunCfg < rhs->second.isLastRunCfg; //"last session" label should be (always) last
- const bool wasRunL = lhs->second.cfgItem.lastRunStats.startTime != 0;
- const bool wasRunR = rhs->second.cfgItem.lastRunStats.startTime != 0;
- if (wasRunL != wasRunR)
- return wasRunL > wasRunR; //move sync jobs that were never run to the back
+ const bool haveResultL = !AFS::isNullPath(lhs->second.cfgItem.lastRunStats.logFilePath);
+ const bool haveResultR = !AFS::isNullPath(rhs->second.cfgItem.lastRunStats.logFilePath);
+ if (haveResultL != haveResultR)
+ return haveResultL > haveResultR; //move sync jobs that were never run to the back
//primary sort order
- if (wasRunL && lhs->second.cfgItem.lastRunStats.syncResult != rhs->second.cfgItem.lastRunStats.syncResult)
+ if (haveResultL && lhs->second.cfgItem.lastRunStats.syncResult != rhs->second.cfgItem.lastRunStats.syncResult)
return makeSortDirection(std::greater(), std::bool_constant<ascending>())(lhs->second.cfgItem.lastRunStats.syncResult, rhs->second.cfgItem.lastRunStats.syncResult);
//secondary sort order
@@ -375,10 +375,10 @@ private:
return utfTo<std::wstring>(item->name);
case ColumnTypeCfg::lastSync:
- if (!item->isLastRunCfg)
+ if (!item->isLastRunCfg && item->cfgItem.lastRunStats.startTime > 0)
{
- if (item->cfgItem.lastRunStats.startTime == 0)
- return std::wstring(1, EN_DASH);
+ //if (item->cfgItem.lastRunStats.startTime == 0)
+ // return std::wstring(1, EN_DASH);
//return utfTo<std::wstring>(formatTime(formatDateTimeTag, getLocalTime(item->cfgItem.lastRunStats.startTime)));
@@ -391,7 +391,7 @@ private:
break;
case ColumnTypeCfg::lastLog:
- if (!item->isLastRunCfg && item->cfgItem.lastRunStats.startTime != 0)
+ if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.lastRunStats.logFilePath))
return getSyncResultLabel(item->cfgItem.lastRunStats.syncResult);
break;
}
@@ -506,7 +506,7 @@ private:
break;
case ColumnTypeCfg::lastLog:
- if (!item->isLastRunCfg && item->cfgItem.lastRunStats.startTime != 0)
+ if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.lastRunStats.logFilePath))
{
const wxImage statusIcon = [&]
{
@@ -654,7 +654,7 @@ private:
break;
case ColumnTypeCfg::lastLog:
- if (!item->isLastRunCfg && item->cfgItem.lastRunStats.startTime != 0)
+ if (!item->isLastRunCfg && !AFS::isNullPath(item->cfgItem.lastRunStats.logFilePath))
{
std::wstring tooltip = getSyncResultLabel(item->cfgItem.lastRunStats.syncResult) + L"\n";
@@ -667,8 +667,10 @@ private:
const int64_t totalTimeSec = std::chrono::duration_cast<std::chrono::seconds>(item->cfgItem.lastRunStats.totalTime).count();
tooltip += TAB_SPACE + _("Total time:") + L' ' + utfTo<std::wstring>(formatTimeSpan(totalTimeSec));
- //if (!AFS::isNullPath(item->cfgItem.lastRunStats.logFilePath))
- // tooltip += L"\n" + AFS::getDisplayPath(item->cfgItem.lastRunStats.logFilePath);
+ //non-native path won't be clickable => at least show in tooltip:
+ if (getNativeItemPath(item->cfgItem.lastRunStats.logFilePath).empty())
+ tooltip += L"\n" + AFS::getDisplayPath(item->cfgItem.lastRunStats.logFilePath);
+
return tooltip;
}
break;
diff --git a/FreeFileSync/Source/ui/cfg_grid.h b/FreeFileSync/Source/ui/cfg_grid.h
index 6a40f190..4a757602 100644
--- a/FreeFileSync/Source/ui/cfg_grid.h
+++ b/FreeFileSync/Source/ui/cfg_grid.h
@@ -18,14 +18,15 @@ namespace fff
{
struct LastRunStats
{
- AbstractPath logFilePath = getNullPath(); //optional
- time_t startTime = 0;
+ time_t startTime = 0; //may be updated separately from log file, e.g. "nothing to sync" after comparison
+ //----------------------------------------------
+ AbstractPath logFilePath = getNullPath(); //optional: available <=> sync took place
TaskResult syncResult = TaskResult::cancelled;
- int itemsProcessed = 0;
- int64_t bytesProcessed = 0;
+ int itemsProcessed = -1;
+ int64_t bytesProcessed = -1;
std::chrono::milliseconds totalTime{};
- int errors = 0;
- int warnings = 0;
+ int errors = -1;
+ int warnings = -1;
};
struct ConfigFileItem
diff --git a/FreeFileSync/Source/ui/folder_pair.h b/FreeFileSync/Source/ui/folder_pair.h
index 9df67c82..73ace920 100644
--- a/FreeFileSync/Source/ui/folder_pair.h
+++ b/FreeFileSync/Source/ui/folder_pair.h
@@ -13,6 +13,7 @@
//#include <wx+/bitmap_button.h>
#include <wx+/image_tools.h>
#include <wx+/image_resources.h>
+//#include <wx+/std_button_layout.h>
//#include "folder_selector.h"
//#include "small_dlgs.h"
//#include "sync_cfg.h"
@@ -23,6 +24,8 @@
namespace fff
{
//basic functionality for handling alternate folder pair configuration: change sync-cfg/filter cfg, right-click context menu, button icons...
+std::wstring getFilterSummaryForTooltip(const FilterConfig& filterCfg);
+
template <class GuiPanel>
class FolderPairPanelBasic : private wxEvtHandler
@@ -61,44 +64,95 @@ private:
setImage(*basicPanel_.m_bpButtonLocalCompCfg, greyScaleIfDisabled(imgCmp_, !!localCmpCfg_));
basicPanel_.m_bpButtonLocalCompCfg->SetToolTip(localCmpCfg_ ?
- _("Local comparison settings") + L" (" + getVariantName(localCmpCfg_->compareVar) + L')' :
+ _("Local comparison settings") + L"\n(" + getVariantName(localCmpCfg_->compareVar) + L')' :
_("Local comparison settings"));
setImage(*basicPanel_.m_bpButtonLocalSyncCfg, greyScaleIfDisabled(imgSync_, !!localSyncCfg_));
basicPanel_.m_bpButtonLocalSyncCfg->SetToolTip(localSyncCfg_ ?
- _("Local synchronization settings") + L" (" + getVariantName(getSyncVariant(localSyncCfg_->directionCfg)) + L')' :
+ _("Local synchronization settings") + L"\n(" + getVariantName(getSyncVariant(localSyncCfg_->directionCfg)) + L')' :
_("Local synchronization settings"));
setImage(*basicPanel_.m_bpButtonLocalFilter, greyScaleIfDisabled(imgFilter_, !isNullFilter(localFilter_)));
- basicPanel_.m_bpButtonLocalFilter->SetToolTip(!isNullFilter(localFilter_) ?
- _("Local filter") + L" (" + _("Active") + L')' :
- _("Local filter") + L" (" + _("None") + L')');
+ basicPanel_.m_bpButtonLocalFilter->SetToolTip(_("Local filter") + getFilterSummaryForTooltip(localFilter_));
}
void onLocalCompCfgContext(wxEvent& event)
{
+ using namespace zen;
+
+ ContextMenu menu;
+
+ auto setVariant = [&](CompareVariant var)
+ {
+ if (!this->localCmpCfg_)
+ this->localCmpCfg_ = CompConfig();
+ this->localCmpCfg_->compareVar = var;
+
+ this->refreshButtons();
+ this->onLocalCompCfgChange();
+ };
+
+ auto addVariantItem = [&](CompareVariant cmpVar, const char* iconName)
+ {
+ const wxImage imgSel = loadImage(iconName, -1 /*maxWidth*/, dipToScreen(getMenuIconDipSize()));
+
+ menu.addItem(getVariantName(cmpVar), [&setVariant, cmpVar] { setVariant(cmpVar); },
+ greyScaleIfDisabled(imgSel, this->localCmpCfg_ && this->localCmpCfg_->compareVar == cmpVar));
+ };
+ addVariantItem(CompareVariant::timeSize, "cmp_time");
+ addVariantItem(CompareVariant::content, "cmp_content");
+ addVariantItem(CompareVariant::size, "cmp_size");
+
+ //----------------------------------------------------------------------------------------
+ menu.addSeparator();
+
auto removeLocalCompCfg = [&]
{
this->localCmpCfg_ = {}; //"this->" galore: workaround GCC compiler bugs
this->refreshButtons();
this->onLocalCompCfgChange();
};
-
- zen::ContextMenu menu;
menu.addItem(_("Remove local settings"), removeLocalCompCfg, wxNullImage, static_cast<bool>(localCmpCfg_));
menu.popup(*basicPanel_.m_bpButtonLocalCompCfg, {basicPanel_.m_bpButtonLocalCompCfg->GetSize().x, 0});
}
void onLocalSyncCfgContext(wxEvent& event)
{
+ using namespace zen;
+
+ ContextMenu menu;
+
+ auto setVariant = [&](SyncVariant var)
+ {
+ if (!this->localSyncCfg_)
+ this->localSyncCfg_ = SyncConfig();
+ this->localSyncCfg_->directionCfg = getDefaultSyncCfg(var);
+
+ this->refreshButtons();
+ this->onLocalSyncCfgChange();
+ };
+
+ auto addVariantItem = [&](SyncVariant syncVar, const char* iconName)
+ {
+ const wxImage imgSel = mirrorIfRtl(loadImage(iconName, -1 /*maxWidth*/, dipToScreen(getMenuIconDipSize())));
+
+ menu.addItem(getVariantName(syncVar), [&setVariant, syncVar] { setVariant(syncVar); },
+ greyScaleIfDisabled(imgSel, this->localSyncCfg_ && getSyncVariant(this->localSyncCfg_->directionCfg) == syncVar));
+ };
+ addVariantItem(SyncVariant::twoWay, "sync_twoway");
+ addVariantItem(SyncVariant::mirror, "sync_mirror");
+ addVariantItem(SyncVariant::update, "sync_update");
+ //addVariantItem(SyncVariant::custom, "sync_custom"); -> doesn't make sense, does it?
+
+ //----------------------------------------------------------------------------------------
+ menu.addSeparator();
+
auto removeLocalSyncCfg = [&]
{
this->localSyncCfg_ = {};
this->refreshButtons();
this->onLocalSyncCfgChange();
};
-
- zen::ContextMenu menu;
menu.addItem(_("Remove local settings"), removeLocalSyncCfg, wxNullImage, static_cast<bool>(localSyncCfg_));
menu.popup(*basicPanel_.m_bpButtonLocalSyncCfg, {basicPanel_.m_bpButtonLocalSyncCfg->GetSize().x, 0});
}
@@ -129,10 +183,10 @@ private:
};
zen::ContextMenu menu;
- menu.addItem( _("Cu&t"), cutFilter, loadImage("item_cut_sicon"), !isNullFilter(localFilter_));
- menu.addSeparator();
menu.addItem( _("&Copy"), copyFilter, loadImage("item_copy_sicon"), !isNullFilter(localFilter_));
menu.addItem( _("&Paste"), pasteFilter, loadImage("item_paste_sicon"), filterCfgOnClipboard.has_value());
+ menu.addSeparator();
+ menu.addItem( _("Cu&t"), cutFilter, loadImage("item_cut_sicon"), !isNullFilter(localFilter_));
menu.popup(*basicPanel_.m_bpButtonLocalFilter, {basicPanel_.m_bpButtonLocalFilter->GetSize().x, 0});
}
@@ -156,6 +210,38 @@ private:
const wxImage imgSync_ = zen::loadImage("options_sync", zen::dipToScreen(20));
const wxImage imgFilter_ = zen::loadImage("options_filter", zen::dipToScreen(20));
};
+
+
+inline
+std::wstring getFilterSummaryForTooltip(const FilterConfig& filterCfg)
+{
+ using namespace zen;
+
+ auto indentLines = [](Zstring str)
+ {
+ std::wstring out;
+ split(str, Zstr('\n'), [&out](ZstringView block)
+ {
+ block = trimCpy(block);
+ if (!block.empty())
+ {
+ out += L'\n';
+ out += TAB_SPACE;
+ out += utfTo<std::wstring>(block);
+ }
+ });
+ return out;
+ };
+
+ std::wstring filterSummary;
+ if (trimCpy(filterCfg.includeFilter) != Zstr("*")) //harmonize with base/path_filter.cpp NameFilter::isNull
+ filterSummary += L"\n\n" + _("Include:") + indentLines(filterCfg.includeFilter);
+
+ if (!trimCpy(filterCfg.excludeFilter).empty())
+ filterSummary += L"\n\n" + _("Exclude:") + indentLines(filterCfg.excludeFilter);
+
+ return filterSummary;
+}
}
#endif //FOLDER_PAIR_H_89341750847252345
diff --git a/FreeFileSync/Source/ui/gui_generated.cpp b/FreeFileSync/Source/ui/gui_generated.cpp
index c377ab5b..b552f0eb 100644
--- a/FreeFileSync/Source/ui/gui_generated.cpp
+++ b/FreeFileSync/Source/ui/gui_generated.cpp
@@ -1633,7 +1633,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelCompSettingsTab->SetSizer( bSizer275 );
m_panelCompSettingsTab->Layout();
bSizer275->Fit( m_panelCompSettingsTab );
- m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), true );
+ m_notebook->AddPage( m_panelCompSettingsTab, _("dummy"), false );
m_panelFilterSettingsTab = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelFilterSettingsTab->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
@@ -1841,7 +1841,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
wxBoxSizer* bSizer302;
bSizer302 = new wxBoxSizer( wxHORIZONTAL );
- m_staticTextFilterDescr = new wxStaticText( m_panel571, wxID_ANY, _("Select filter rules to exclude certain files from synchronization. Enter file paths relative to their corresponding folder pair."), wxDefaultPosition, wxSize( -1, -1 ), 0 );
+ m_staticTextFilterDescr = new wxStaticText( m_panel571, wxID_ANY, _("Select filter rules to exclude certain files from synchronization.\nEnter file paths relative to their corresponding folder pair."), wxDefaultPosition, wxSize( -1, -1 ), 0 );
m_staticTextFilterDescr->Wrap( -1 );
m_staticTextFilterDescr->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
@@ -1994,18 +1994,18 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
bSizer3121 = new wxBoxSizer( wxHORIZONTAL );
m_bitmapMoveLeft = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 );
- m_bitmapMoveLeft->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+ m_bitmapMoveLeft->SetToolTip( _("- Available not before the *second* synchronization\n- Not supported by all file systems") );
bSizer3121->Add( m_bitmapMoveLeft, 0, wxALIGN_CENTER_VERTICAL, 5 );
m_bitmapMoveRight = new wxStaticBitmap( m_panelSyncSettings, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1, -1 ), 0 );
- m_bitmapMoveRight->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+ m_bitmapMoveRight->SetToolTip( _("- Available not before the *second* synchronization\n- Not supported by all file systems") );
bSizer3121->Add( m_bitmapMoveRight, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
m_staticTextDetectMove = new wxStaticText( m_panelSyncSettings, wxID_ANY, _("Detect moved files"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticTextDetectMove->Wrap( -1 );
- m_staticTextDetectMove->SetToolTip( _("- Not supported by all file systems\n- Requires and creates database files\n- Detection not available for first sync") );
+ m_staticTextDetectMove->SetToolTip( _("- Available not before the *second* synchronization\n- Not supported by all file systems") );
bSizer3121->Add( m_staticTextDetectMove, 0, wxALIGN_CENTER_VERTICAL, 5 );
@@ -2536,7 +2536,7 @@ ConfigDlgGenerated::ConfigDlgGenerated( wxWindow* parent, wxWindowID id, const w
m_panelSyncSettingsTab->SetSizer( bSizer276 );
m_panelSyncSettingsTab->Layout();
bSizer276->Fit( m_panelSyncSettingsTab );
- m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), false );
+ m_notebook->AddPage( m_panelSyncSettingsTab, _("dummy"), true );
bSizer190->Add( m_notebook, 1, wxEXPAND, 5 );
diff --git a/FreeFileSync/Source/ui/log_panel.cpp b/FreeFileSync/Source/ui/log_panel.cpp
index e69e19c7..524ebff2 100644
--- a/FreeFileSync/Source/ui/log_panel.cpp
+++ b/FreeFileSync/Source/ui/log_panel.cpp
@@ -53,7 +53,7 @@ enum class ColumnTypeLog
class fff::MessageView
{
public:
- MessageView(const SharedRef<const ErrorLog>& log) : log_(log) {}
+ explicit MessageView(const SharedRef<const ErrorLog>& log) : log_(log) {}
size_t rowsOnView() const { return viewRef_.size(); }
@@ -114,7 +114,7 @@ private:
auto it1 = message.begin();
for (;;)
{
- auto it2 = std::find_if(it1, message.end(), [](char c) { return c == '\n'; });
+ auto it2 = std::find_if(it1, message.end(), [](const char c) { return c == '\n'; });
if (textRow == 0)
return makeStringView(it1, it2 - it1);
diff --git a/FreeFileSync/Source/ui/main_dlg.cpp b/FreeFileSync/Source/ui/main_dlg.cpp
index 6ea78d9b..6a3c9660 100644
--- a/FreeFileSync/Source/ui/main_dlg.cpp
+++ b/FreeFileSync/Source/ui/main_dlg.cpp
@@ -4431,10 +4431,10 @@ void MainDialog::onGlobalFilterContext(wxEvent& event)
};
ContextMenu menu;
- menu.addItem( _("Cu&t"), cutFilter, loadImage("item_cut_sicon"), !isNullFilter(currentCfg_.mainCfg.globalFilter));
- menu.addSeparator();
menu.addItem( _("&Copy"), copyFilter, loadImage("item_copy_sicon"), !isNullFilter(currentCfg_.mainCfg.globalFilter));
menu.addItem( _("&Paste"), pasteFilter, loadImage("item_paste_sicon"), filterCfgOnClipboard.has_value());
+ menu.addSeparator();
+ menu.addItem( _("Cu&t"), cutFilter, loadImage("item_cut_sicon"), !isNullFilter(currentCfg_.mainCfg.globalFilter));
menu.popup(*m_bpButtonFilterContext, {m_bpButtonFilterContext->GetSize().x, 0});
}
@@ -4550,8 +4550,7 @@ void MainDialog::updateGlobalFilterButton()
//global filter: test for Null-filter
setImage(*m_bpButtonFilter, greyScaleIfDisabled(loadImage("options_filter"), !isNullFilter(currentCfg_.mainCfg.globalFilter)));
- const std::wstring status = !isNullFilter(currentCfg_.mainCfg.globalFilter) ? _("Active") : _("None");
- m_bpButtonFilter->SetToolTip(_("Filter") + L" (F7) (" + status + L')');
+ m_bpButtonFilter->SetToolTip(_("Filter") + L" (F7)" + getFilterSummaryForTooltip(currentCfg_.mainCfg.globalFilter));
//m_bpButtonFilterContext->SetToolTip(m_bpButtonFilter->GetToolTipText());
}
@@ -5029,8 +5028,8 @@ void MainDialog::onStartSync(wxCommandEvent& event)
cfggrid::getDataView(*m_gridCfgHistory).setLastRunStats(activeConfigFiles_,
{
- logFilePath,
std::chrono::system_clock::to_time_t(fullSummary.startTime),
+ logFilePath,
fullSummary.result,
fullSummary.statsProcessed.items,
fullSummary.statsProcessed.bytes,
@@ -6366,7 +6365,7 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event)
if (automaticUpdateCheckDue(globalCfg_.lastUpdateCheck))
{
- flashStatusInfo(_("Searching for program updates..."));
+ flashStatusInfo(_("Searching for software updates..."));
guiQueue_.processAsync([resultPrep = automaticUpdateCheckPrepare(*this) /*prepare on main thread*/]
{ return automaticUpdateCheckRunAsync(resultPrep.ref()); }, //run on worker thread: (long-running part of the check)
@@ -6379,7 +6378,7 @@ void MainDialog::onStartupUpdateCheck(wxIdleEvent& event)
showNewVersionReminder();
if (globalCfg_.lastUpdateCheck == lastUpdateCheckOld)
- flashStatusInfo(_("Update check failed!"));
+ flashStatusInfo(_("Software update check failed!"));
});
}
else
diff --git a/FreeFileSync/Source/ui/small_dlgs.cpp b/FreeFileSync/Source/ui/small_dlgs.cpp
index 374c7493..ac4d2405 100644
--- a/FreeFileSync/Source/ui/small_dlgs.cpp
+++ b/FreeFileSync/Source/ui/small_dlgs.cpp
@@ -1357,7 +1357,7 @@ private:
{[](const XmlGlobalSettings& gs){ return gs.warnDlgs.warnDirectoryLockFailed; },
[]( XmlGlobalSettings& gs, bool show){ gs.warnDlgs.warnDirectoryLockFailed = show; }, _("Cannot set directory locks for the following folders:")},
{[](const XmlGlobalSettings& gs){ return gs.warnDlgs.warnVersioningFolderPartOfSync; },
- []( XmlGlobalSettings& gs, bool show){ gs.warnDlgs.warnVersioningFolderPartOfSync = show; }, _("The versioning folder is part of the synchronization.") + L' ' + _("The folder should be excluded via filter.")},
+ []( XmlGlobalSettings& gs, bool show){ gs.warnDlgs.warnVersioningFolderPartOfSync = show; }, _("The versioning folder must not be part of the synchronization.") + L' ' + _("The folder should be excluded via filter.")},
//*INDENT-ON*
};
diff --git a/FreeFileSync/Source/ui/sync_cfg.cpp b/FreeFileSync/Source/ui/sync_cfg.cpp
index 790825ac..c7da4516 100644
--- a/FreeFileSync/Source/ui/sync_cfg.cpp
+++ b/FreeFileSync/Source/ui/sync_cfg.cpp
@@ -34,7 +34,7 @@ using namespace fff;
namespace
{
-const int CFG_DESCRIPTION_WIDTH_DIP = 230;
+const int CFG_DESCRIPTION_WIDTH_DIP = 250;
const wchar_t arrowRight[] = L"\u2192"; //"RIGHTWARDS ARROW"
@@ -567,8 +567,6 @@ globalLogFolderPhrase_(globalLogFolderPhrase)
setDefaultWidth(*m_spinCtrlMaxSize);
setDefaultWidth(*m_spinCtrlTimespan);
- m_staticTextFilterDescr->Wrap(dipToWxsize(450));
-
setImage(*m_bpButtonDefaultContext, mirrorIfRtl(loadImage("button_arrow_right")));
enumTimeDescr_.
diff --git a/FreeFileSync/Source/ui/version_check.cpp b/FreeFileSync/Source/ui/version_check.cpp
index 72f35168..6219d083 100644
--- a/FreeFileSync/Source/ui/version_check.cpp
+++ b/FreeFileSync/Source/ui/version_check.cpp
@@ -148,7 +148,7 @@ void showUpdateAvailableDialog(wxWindow* parent, const std::string& onlineVersio
switch (showConfirmationDialog(parent, DialogInfoType::info, PopupDialogCfg().
setIcon(loadImage("FreeFileSync", dipToScreen(48))).
- setTitle(_("Check for Program Updates")).
+ setTitle(_("Check for Software Updates")).
setMainInstructions(replaceCpy(_("FreeFileSync %x is available!"), L"%x", utfTo<std::wstring>(onlineVersion)) + L"\n\n" + _("Download now?")).
setDetailInstructions(updateDetailsMsg), _("&Download")))
{
@@ -167,7 +167,7 @@ std::string getOnlineVersion(const std::vector<std::pair<std::string, std::strin
ffsUpdateCheckUserAgent, Zstring() /*caCertFilePath*/).readAll(nullptr /*notifyUnbufferedIO*/); //throw SysError
if (response.empty() ||
- !std::all_of(response.begin(), response.end(), [](char c) { return isDigit(c) || c == FFS_VERSION_SEPARATOR; }) ||
+ !std::all_of(response.begin(), response.end(), [](const char c) { return isDigit(c) || c == FFS_VERSION_SEPARATOR; }) ||
startsWith(response, FFS_VERSION_SEPARATOR) ||
endsWith(response, FFS_VERSION_SEPARATOR) ||
contains(response, std::string() + FFS_VERSION_SEPARATOR + FFS_VERSION_SEPARATOR))
@@ -180,7 +180,7 @@ std::string getOnlineVersion(const std::vector<std::pair<std::string, std::strin
std::string getUnknownVersionTag()
{
- return '<' + utfTo<std::string>(_("version unknown")) + '>';
+ return '<' + utfTo<std::string>(_("unknown version")) + '>';
}
}
@@ -217,7 +217,7 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion)
else
showNotificationDialog(&parent, DialogInfoType::info, PopupDialogCfg().
setIcon(loadImage("update_check")).
- setTitle(_("Check for Program Updates")).
+ setTitle(_("Check for Software Updates")).
setMainInstructions(_("FreeFileSync is up-to-date.")));
}
catch (const SysError& e)
@@ -227,7 +227,7 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion)
lastOnlineVersion = getUnknownVersionTag();
switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
- setTitle(_("Check for Program Updates")).
+ setTitle(_("Check for Software Updates")).
setMainInstructions(_("Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?")).
setDetailInstructions(e.toString()), _("&Check"), _("&Retry")))
{
@@ -243,7 +243,7 @@ void fff::checkForUpdateNow(wxWindow& parent, std::string& lastOnlineVersion)
}
else
switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
- setTitle(_("Check for Program Updates")).
+ setTitle(_("Check for Software Updates")).
setMainInstructions(replaceCpy(_("Unable to connect to %x."), L"%x", L"freefilesync.org")).
setDetailInstructions(e.toString()), _("&Retry")))
{
@@ -327,7 +327,7 @@ void fff::automaticUpdateCheckEval(wxWindow& parent, time_t& lastUpdateCheck, st
{
if (lastOnlineVersion != getUnknownVersionTag())
switch (showConfirmationDialog(&parent, DialogInfoType::error, PopupDialogCfg().
- setTitle(_("Check for Program Updates")).
+ setTitle(_("Check for Software Updates")).
setMainInstructions(_("Cannot find current FreeFileSync version number online. A newer version is likely available. Check manually now?")).
setDetailInstructions(result.error->toString()),
_("&Check"), _("&Retry")))
diff --git a/FreeFileSync/Source/version/version.h b/FreeFileSync/Source/version/version.h
index 852cbd04..6d4418f9 100644
--- a/FreeFileSync/Source/version/version.h
+++ b/FreeFileSync/Source/version/version.h
@@ -3,7 +3,7 @@
namespace fff
{
-const char ffsVersion[] = "13.8"; //internal linkage!
+const char ffsVersion[] = "13.9"; //internal linkage!
const char FFS_VERSION_SEPARATOR = '.';
}
diff --git a/zen/file_path.cpp b/zen/file_path.cpp
index 5941689e..b19aba45 100644
--- a/zen/file_path.cpp
+++ b/zen/file_path.cpp
@@ -50,7 +50,7 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
{
Zstring tmp(itemPath.begin() + strLength("/run/user/"), itemPath.end());
tmp = beforeFirst(tmp, "/gvfs/", IfNotFoundReturn::none);
- if (!tmp.empty() && std::all_of(tmp.begin(), tmp.end(), [](char c) { return isDigit(c); }))
+ if (!tmp.empty() && std::all_of(tmp.begin(), tmp.end(), [](const char c) { return isDigit(c); }))
/**/pc = doParse(6 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
}
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index 8564e612..75075b80 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -6,7 +6,7 @@
#include "file_traverser.h"
#include "file_error.h"
- #include "file_access.h"
+#include "file_access.h"
#include <sys/stat.h>
diff --git a/zen/http.cpp b/zen/http.cpp
index fcf69fb3..04ee42fd 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -513,7 +513,7 @@ bool zen::isValidEmail(const std::string_view& email)
contains(local, '\\'); //e.g. "t\@st@email.com"
if (!quoted)
for (const std::string_view& comp : splitCpy(local, '.', SplitOnEmpty::allow))
- if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](char c)
+ if (comp.empty() || !std::all_of(comp.begin(), comp.end(), [](const char c)
{
constexpr std::string_view printable("!#$%&'*+-/=?^_`{|}~");
return isAsciiAlpha(c) || isDigit(c) || !isAsciiChar(c) ||
@@ -531,7 +531,7 @@ bool zen::isValidEmail(const std::string_view& email)
for (const std::string_view& comp : splitCpy(domain, '.', SplitOnEmpty::allow))
if (comp.empty() || comp.size() > 63 ||
- !std::all_of(comp.begin(), comp.end(), [](char c) { return isAsciiAlpha(c) ||isDigit(c) || !isAsciiChar(c) || c == '-'; }))
+ !std::all_of(comp.begin(), comp.end(), [](const char c) { return isAsciiAlpha(c) ||isDigit(c) || !isAsciiChar(c) || c == '-'; }))
return false;
}
diff --git a/zen/json.h b/zen/json.h
index 0ac2ee61..8e7bca48 100644
--- a/zen/json.h
+++ b/zen/json.h
@@ -140,11 +140,11 @@ namespace
{
UtfDecoder<impl::Char16> decoder(utf16Buf.c_str(), utf16Buf.size());
while (std::optional<impl::CodePoint> cp = decoder.getNext())
- codePointToUtf<char>(*cp, [&](char c) { output += c; });
+ codePointToUtf<char>(*cp, [&](const char c) { output += c; });
utf16Buf.clear();
}
};
- auto writeOut = [&](char c)
+ auto writeOut = [&](const char c)
{
flushUtf16();
output += c;
@@ -419,8 +419,8 @@ private:
Scanner (const Scanner&) = delete;
Scanner& operator=(const Scanner&) = delete;
- static bool isJsonWhiteSpace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; }
- static bool isJsonNumDigit (char c) { return ('0' <= c && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e'|| c == 'E'; }
+ static bool isJsonWhiteSpace(const char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; }
+ static bool isJsonNumDigit (const char c) { return ('0' <= c && c <= '9') || c == '-' || c == '+' || c == '.' || c == 'e'|| c == 'E'; }
bool startsWith(const std::string& prefix) const
{
diff --git a/zen/string_tools.h b/zen/string_tools.h
index b95a2055..d7d0804f 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -431,7 +431,7 @@ template <class S, class Char, class Function> inline
void split(const S& str, Char delimiter, Function onStringPart)
{
static_assert(std::is_same_v<GetCharTypeT<S>, Char>);
- split2(str, [delimiter](Char c) { return c == delimiter; }, onStringPart);
+ split2(str, [delimiter](const Char c) { return c == delimiter; }, onStringPart);
}
@@ -441,7 +441,7 @@ std::vector<S> splitCpy(const S& str, Char delimiter, SplitOnEmpty soe)
static_assert(std::is_same_v<GetCharTypeT<S>, Char>);
std::vector<S> output;
- split2(str, [delimiter](Char c) { return c == delimiter; }, [&, soe](std::basic_string_view<Char> block)
+ split2(str, [delimiter](const Char c) { return c == delimiter; }, [&, soe](std::basic_string_view<Char> block)
{
if (!block.empty() || soe == SplitOnEmpty::allow)
output.emplace_back(block.data(), block.size());
@@ -921,7 +921,7 @@ std::pair<char, char> hexify(unsigned char c, bool upperCase)
inline //unhexify beats "::sscanf(&it[3], "%02X", &tmp)" by a factor of 3000 for ~250000 calls!!!
char unhexify(char high, char low)
{
- auto unhexifyDigit = [](char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15]
+ auto unhexifyDigit = [](const char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15]
{
if ('0' <= hex && hex <= '9') //no signed/unsigned char problem here!
return hex - '0';
diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp
index 705fbade..03668709 100644
--- a/zen/sys_version.cpp
+++ b/zen/sys_version.cpp
@@ -55,8 +55,8 @@ OsVersionDetail zen::getOsVersionDetail() //throw SysError
osVersion = utfTo<std::wstring>(afterFirst(line, '=', IfNotFoundReturn::none));
//PRETTY_NAME? too wordy! e.g. "Fedora 17 (Beefy Miracle)"
});
- trim(osName, TrimSide::both, [](char c) { return c == L'"' || c == L'\''; });
- trim(osVersion, TrimSide::both, [](char c) { return c == L'"' || c == L'\''; });
+ trim(osName, TrimSide::both, [](const char c) { return c == L'"' || c == L'\''; });
+ trim(osVersion, TrimSide::both, [](const char c) { return c == L'"' || c == L'\''; });
}
if (osName.empty())
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index 019973e9..d011eda0 100644
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -104,7 +104,7 @@ Zstring getUpperCaseNonAscii(const Zstring& str)
UtfDecoder<char> decoder(strNorm.c_str(), strNorm.size());
while (const std::optional<impl::CodePoint> cp = decoder.getNext())
- codePointToUtf<char>(::g_unichar_toupper(*cp), [&](char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent!
+ codePointToUtf<char>(::g_unichar_toupper(*cp), [&](const char c) { output += c; }); //don't use std::towupper: *incomplete* and locale-dependent!
static_assert(sizeof(impl::CodePoint) == sizeof(gunichar));
return output;
diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h
index d34b2fff..6c7959b4 100644
--- a/zenXml/zenxml/parser.h
+++ b/zenXml/zenxml/parser.h
@@ -110,7 +110,7 @@ std::string normalize(const std::string_view& str, Predicate pred) //pred: unary
inline
std::string normalizeName(const std::string& str)
{
- /*const*/ std::string nameFmt = normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '"'; });
+ /*const*/ std::string nameFmt = normalize(str, [](const char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '"'; });
assert(!nameFmt.empty());
return nameFmt;
}
@@ -118,13 +118,13 @@ std::string normalizeName(const std::string& str)
inline
std::string normalizeElementValue(const std::string& str)
{
- return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32; });
+ return normalize(str, [](const char c) { return static_cast<unsigned char>(c) < 32; });
}
inline
std::string normalizeAttribValue(const std::string& str)
{
- return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '"'; });
+ return normalize(str, [](const char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '"'; });
}
@@ -343,7 +343,7 @@ public:
return it->second;
}
- const auto itNameEnd = std::find_if(pos_, stream_.end(), [](char c)
+ const auto itNameEnd = std::find_if(pos_, stream_.end(), [](const char c)
{
return c == '<' ||
c == '>' ||
@@ -367,7 +367,7 @@ public:
std::string extractElementValue()
{
- auto it = std::find_if(pos_, stream_.end(), [](char c)
+ auto it = std::find_if(pos_, stream_.end(), [](const char c)
{
return c == '<' ||
c == '>';
@@ -379,7 +379,7 @@ public:
std::string extractAttributeValue()
{
- auto it = std::find_if(pos_, stream_.end(), [](char c)
+ auto it = std::find_if(pos_, stream_.end(), [](const char c)
{
return c == '<' ||
c == '>' ||
bgstack15