diff options
Diffstat (limited to 'shared')
33 files changed, 2713 insertions, 1112 deletions
diff --git a/shared/ShadowCopy/LockFile.cpp b/shared/ShadowCopy/LockFile.cpp new file mode 100644 index 00000000..0761a3e3 --- /dev/null +++ b/shared/ShadowCopy/LockFile.cpp @@ -0,0 +1,44 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// + +#include <string> +#include <iostream> +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +int wmain(int argc, wchar_t* argv[]) +{ + if (argc <= 1) + { + std::wcout << "Please enter the filename to be locked as %1 parameter!" << "\n\n"; + system("pause"); + return -1; + } + std::wstring filename = argv[1]; + + //obtain exclusive lock on test file + HANDLE hFile = ::CreateFile(filename.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + std::wcout << "Error obtaining exclusive lock on test file: " << filename << "\n\n"; + system("pause"); + return -1; + } + + std::wcout << "File " << filename << " is locked! Press a key to unlock." << "\n\n"; + system("pause"); + + ::CloseHandle(hFile); + return 0; +} + diff --git a/shared/custom_button.cpp b/shared/custom_button.cpp index de057dc5..df65a401 100644 --- a/shared/custom_button.cpp +++ b/shared/custom_button.cpp @@ -8,6 +8,8 @@ #include <wx/dcmemory.h> #include <wx/image.h> #include <algorithm> +#include <limits> +#include <cmath> wxButtonWithImage::wxButtonWithImage(wxWindow* parent, @@ -50,23 +52,29 @@ void wxButtonWithImage::setBitmapBack(const wxBitmap& bitmap, unsigned spaceBefo } -void makeWhiteTransparent(const wxColor exceptColor, wxImage& image) +void makeWhiteTransparent(wxImage& image) //assume black text on white background { - unsigned char* alphaData = image.GetAlpha(); - if (alphaData) + unsigned char* alphaFirst = image.GetAlpha(); + if (alphaFirst) { - assert(exceptColor.Red() != 255); + unsigned char* alphaLast = alphaFirst + image.GetWidth() * image.GetHeight(); - unsigned char exCol = exceptColor.Red(); //alpha value can be extracted from any one of (red/green/blue) - unsigned char* imageStart = image.GetData(); + //dist(black, white) + double distBlackWhite = std::sqrt(3.0 * 255 * 255); - unsigned char* j = alphaData; - const unsigned char* const rowEnd = j + image.GetWidth() * image.GetHeight(); - while (j != rowEnd) + const unsigned char* bytePos = image.GetData(); + + for (unsigned char* j = alphaFirst; j != alphaLast; ++j) { - const unsigned char* imagePixel = imageStart + (j - alphaData) * 3; //each pixel consists of three chars - //exceptColor(red,green,blue) becomes fully opaque(255), while white(255,255,255) becomes transparent(0) - *(j++) = ((255 - imagePixel[0]) * wxIMAGE_ALPHA_OPAQUE) / (255 - exCol); + unsigned char r = *bytePos++; // + unsigned char g = *bytePos++; //each pixel consists of three chars + unsigned char b = *bytePos++; // + + //dist((r,g,b), white) + double distColWhite = std::sqrt((255.0 - r) * (255.0 - r) + (255.0 - g) * (255.0 - g) + (255.0 - b) * (255.0 - b)); + + //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0) + *j = distColWhite / distBlackWhite * wxIMAGE_ALPHA_OPAQUE; } } } @@ -132,8 +140,11 @@ void linearInterpolation(wxImage& img) } */ + wxBitmap wxButtonWithImage::createBitmapFromText(const wxString& text) { + //wxDC::DrawLabel() doesn't respect alpha channel at all => apply workaround to calculate alpha values manually: + if (text.empty()) return wxBitmap(); @@ -143,29 +154,32 @@ wxBitmap wxButtonWithImage::createBitmapFromText(const wxString& text) wxSize sizeNeeded = getSizeNeeded(text, currentFont); wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight()); - wxMemoryDC dc; - dc.SelectObject(newBitmap); - - //set up white background - dc.SetBackground(*wxWHITE_BRUSH); - dc.Clear(); - - //find position of accelerator - int indexAccel = -1; - size_t accelPos; - wxString textLabelFormatted = text; - if ((accelPos = text.find(wxT("&"))) != wxString::npos) { - textLabelFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator - indexAccel = static_cast<int>(accelPos); - } + wxMemoryDC dc; + dc.SelectObject(newBitmap); + + //set up white background + dc.SetBackground(*wxWHITE_BRUSH); + dc.Clear(); + + //find position of accelerator + int indexAccel = -1; + size_t accelPos; + wxString textLabelFormatted = text; + if ((accelPos = text.find(wxT("&"))) != wxString::npos) + { + textLabelFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator + indexAccel = static_cast<int>(accelPos); + } + + dc.SetTextForeground(*wxBLACK); //for use in makeWhiteTransparent + dc.SetTextBackground(*wxWHITE); // + dc.SetFont(currentFont); - dc.SetTextForeground(textColor); - dc.SetTextBackground(*wxWHITE); - dc.SetFont(currentFont); - dc.DrawLabel(textLabelFormatted, wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel); + dc.DrawLabel(textLabelFormatted, wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel); - dc.SelectObject(wxNullBitmap); + dc.SelectObject(wxNullBitmap); + } //add alpha channel to image wxImage finalImage(newBitmap.ConvertToImage()); @@ -173,8 +187,18 @@ wxBitmap wxButtonWithImage::createBitmapFromText(const wxString& text) //linearInterpolation(finalImage); - //make white background transparent - makeWhiteTransparent(textColor, finalImage); + //calculate values for alpha channel + makeWhiteTransparent(finalImage); + + //now apply real text color + unsigned char* bytePos = finalImage.GetData(); + const int pixelCount = finalImage.GetWidth() * finalImage.GetHeight(); + for (int i = 0; i < pixelCount; ++ i) + { + *bytePos++ = textColor.Red(); + *bytePos++ = textColor.Green(); + *bytePos++ = textColor.Blue(); + } return wxBitmap(finalImage); } diff --git a/shared/dir_name.cpp b/shared/dir_name.cpp index 5fd4eab2..3b0c3e82 100644 --- a/shared/dir_name.cpp +++ b/shared/dir_name.cpp @@ -8,13 +8,14 @@ #include <wx/dnd.h> #include <wx/window.h> #include <wx/textctrl.h> -#include <wx/filename.h> +#include <wx/statbox.h> #include "file_handling.h" +#include "resolve_path.h" #include "string_conv.h" #include "check_exist.h" #include "util.h" +#include "i18n.h" #include "system_constants.h" -#include <wx/statbox.h> //define new event type @@ -25,10 +26,9 @@ typedef void (wxEvtHandler::*FFSFileDropEventFunction)(FFSFileDropEvent&); #define FFSFileDropEventHandler(func) \ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSFileDropEventFunction, &func) -class FFSFileDropEvent : public wxCommandEvent +struct FFSFileDropEvent : public wxCommandEvent { -public: - FFSFileDropEvent(const std::vector<wxString>& filesDropped, const wxWindow* dropWindow) : + FFSFileDropEvent(const std::vector<wxString>& filesDropped, const wxWindow& dropWindow) : wxCommandEvent(FFS_DROP_FILE_EVENT), filesDropped_(filesDropped), dropWindow_(dropWindow) {} @@ -39,7 +39,7 @@ public: } const std::vector<wxString> filesDropped_; - const wxWindow* dropWindow_; + const wxWindow& dropWindow_; }; @@ -78,11 +78,12 @@ void setDirectoryName(const wxString& dirname, wxTextCtrl* txtCtrl, wxDirPickerCtrl* dirPicker, wxWindow& tooltipWnd, + wxStaticBoxSizer* staticBox, size_t timeout = 200) //pointers are optional { if (txtCtrl) txtCtrl->ChangeValue(dirname); - setDirectoryNameImpl(dirname, dirPicker, tooltipWnd, NULL, timeout); + setDirectoryNameImpl(dirname, dirPicker, tooltipWnd, staticBox, timeout); } @@ -107,8 +108,7 @@ void setDirectoryName(const wxString& dirname, class WindowDropTarget : public wxFileDropTarget { public: - WindowDropTarget(wxWindow* dropWindow) : - dropWindow_(dropWindow) {} + WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {} virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) { @@ -120,25 +120,24 @@ public: { //create a custom event on drop window: execute event after file dropping is completed! (e.g. after mouse is released) FFSFileDropEvent evt(filenames, dropWindow_); - dropWindow_->GetEventHandler()->AddPendingEvent(evt); + dropWindow_.GetEventHandler()->AddPendingEvent(evt); } return false; } private: - wxWindow* dropWindow_; + wxWindow& dropWindow_; }; //############################################################################################################## using ffs3::DirectoryNameMainDlg; -DirectoryNameMainDlg::DirectoryNameMainDlg( - wxWindow* dropWindow1, - wxWindow* dropWindow2, - wxDirPickerCtrl* dirPicker, - wxComboBox* dirName, - wxStaticBoxSizer* staticBox) : +DirectoryNameMainDlg::DirectoryNameMainDlg(wxWindow& dropWindow1, + wxWindow& dropWindow2, + wxDirPickerCtrl& dirPicker, + wxComboBox& dirName, + wxStaticBoxSizer& staticBox) : dropWindow1_(dropWindow1), dropWindow2_(dropWindow2), dirPicker_(dirPicker), @@ -146,16 +145,16 @@ DirectoryNameMainDlg::DirectoryNameMainDlg( staticBox_(staticBox) { //prepare drag & drop - dropWindow1->SetDropTarget(new WindowDropTarget(dropWindow1)); //takes ownership - dropWindow2->SetDropTarget(new WindowDropTarget(dropWindow2)); //takes ownership + dropWindow1.SetDropTarget(new WindowDropTarget(dropWindow1)); //takes ownership + dropWindow2.SetDropTarget(new WindowDropTarget(dropWindow2)); //takes ownership //redirect drag & drop event back to this class - dropWindow1->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryNameMainDlg::OnFilesDropped), NULL, this); - dropWindow2->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryNameMainDlg::OnFilesDropped), NULL, this); + dropWindow1.Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryNameMainDlg::OnFilesDropped), NULL, this); + dropWindow2.Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryNameMainDlg::OnFilesDropped), NULL, this); //keep dirPicker and dirName synchronous - dirName-> Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryNameMainDlg::OnWriteDirManually), NULL, this ); - dirPicker->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(DirectoryNameMainDlg::OnDirSelected), NULL, this ); + dirName .Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryNameMainDlg::OnWriteDirManually), NULL, this ); + dirPicker.Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(DirectoryNameMainDlg::OnDirSelected), NULL, this ); } @@ -164,19 +163,19 @@ void DirectoryNameMainDlg::OnFilesDropped(FFSFileDropEvent& event) if (event.filesDropped_.empty()) return; - if (this->dropWindow1_ == event.dropWindow_ || //file may be dropped on window 1 or 2 - this->dropWindow2_ == event.dropWindow_) + if (&dropWindow1_ == &event.dropWindow_ || //file may be dropped on window 1 or 2 + &dropWindow2_ == &event.dropWindow_) { if (AcceptDrop(event.filesDropped_)) { - wxString fileName = event.filesDropped_[0]; - if (wxDirExists(fileName)) - setDirectoryName(fileName, dirName_, dirPicker_, *dirName_, *staticBox_); + Zstring fileName = wxToZ(event.filesDropped_[0]); + if (dirExists(fileName)) + setDirectoryName(zToWx(fileName), &dirName_, &dirPicker_, dirName_, staticBox_); else { - fileName = wxFileName(fileName).GetPath(); - if (wxDirExists(fileName)) - setDirectoryName(fileName, dirName_, dirPicker_, *dirName_, *staticBox_); + fileName = fileName.BeforeLast(common::FILE_NAME_SEPARATOR); + if (dirExists(fileName)) + setDirectoryName(zToWx(fileName), &dirName_, &dirPicker_, dirName_, staticBox_); } } } @@ -185,7 +184,7 @@ void DirectoryNameMainDlg::OnFilesDropped(FFSFileDropEvent& event) void DirectoryNameMainDlg::OnWriteDirManually(wxCommandEvent& event) { - setDirectoryName(event.GetString(), NULL, dirPicker_, *dirName_, *staticBox_, 100); //potentially slow network access: wait 100 ms at most + setDirectoryName(event.GetString(), NULL, &dirPicker_, dirName_, staticBox_, 100); //potentially slow network access: wait 100 ms at most event.Skip(); } @@ -193,7 +192,7 @@ void DirectoryNameMainDlg::OnWriteDirManually(wxCommandEvent& event) void DirectoryNameMainDlg::OnDirSelected(wxFileDirPickerEvent& event) { const wxString newPath = event.GetPath(); - setDirectoryName(newPath, dirName_, NULL, *dirName_, *staticBox_); + setDirectoryName(newPath, &dirName_, NULL, dirName_, staticBox_); event.Skip(); } @@ -201,35 +200,37 @@ void DirectoryNameMainDlg::OnDirSelected(wxFileDirPickerEvent& event) Zstring DirectoryNameMainDlg::getName() const { - return wxToZ(dirName_->GetValue()); + return wxToZ(dirName_.GetValue()); } void DirectoryNameMainDlg::setName(const Zstring& dirname) { - setDirectoryName(zToWx(dirname), dirName_, dirPicker_, *dirName_, *staticBox_); + setDirectoryName(zToWx(dirname), &dirName_, &dirPicker_, dirName_, staticBox_); } //############################################################################################################## using ffs3::DirectoryName; -DirectoryName::DirectoryName(wxWindow* dropWindow, - wxDirPickerCtrl* dirPicker, - wxTextCtrl* dirName) : +DirectoryName::DirectoryName(wxWindow& dropWindow, + wxDirPickerCtrl& dirPicker, + wxTextCtrl& dirName, + wxStaticBoxSizer* staticBox) : dropWindow_(dropWindow), dirPicker_(dirPicker), - dirName_(dirName) + dirName_(dirName), + staticBox_(staticBox) { //prepare drag & drop - dropWindow->SetDropTarget(new WindowDropTarget(dropWindow)); //takes ownership + dropWindow.SetDropTarget(new WindowDropTarget(dropWindow)); //takes ownership //redirect drag & drop event back to this class - dropWindow->Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryName::OnFilesDropped), NULL, this); + dropWindow.Connect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(DirectoryName::OnFilesDropped), NULL, this); //keep dirPicker and dirName synchronous - dirName->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryName::OnWriteDirManually ), NULL, this ); - dirPicker->Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( DirectoryName::OnDirSelected ), NULL, this ); + dirName.Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DirectoryName::OnWriteDirManually ), NULL, this ); + dirPicker.Connect( wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler( DirectoryName::OnDirSelected ), NULL, this ); } @@ -238,16 +239,16 @@ void DirectoryName::OnFilesDropped(FFSFileDropEvent& event) if (event.filesDropped_.empty()) return; - if (this->dropWindow_ == event.dropWindow_) + if (&dropWindow_ == &event.dropWindow_) { - wxString fileName = event.filesDropped_[0]; - if (wxDirExists(fileName)) - setDirectoryName(fileName, dirName_, dirPicker_, *dirName_); + Zstring fileName = wxToZ(event.filesDropped_[0]); + if (dirExists(fileName)) + setDirectoryName(zToWx(fileName), &dirName_, &dirPicker_, dirName_, staticBox_); else { - fileName = wxFileName(fileName).GetPath(); - if (wxDirExists(fileName)) - setDirectoryName(fileName, dirName_, dirPicker_, *dirName_); + fileName = fileName.BeforeLast(common::FILE_NAME_SEPARATOR); + if (dirExists(fileName)) + setDirectoryName(zToWx(fileName), &dirName_, &dirPicker_, dirName_, staticBox_); } } } @@ -255,7 +256,7 @@ void DirectoryName::OnFilesDropped(FFSFileDropEvent& event) void DirectoryName::OnWriteDirManually(wxCommandEvent& event) { - setDirectoryName(event.GetString(), NULL, dirPicker_, *dirName_, 100); //potentially slow network access: wait 100 ms at most + setDirectoryName(event.GetString(), NULL, &dirPicker_, dirName_, staticBox_, 100); //potentially slow network access: wait 100 ms at most event.Skip(); } @@ -263,7 +264,7 @@ void DirectoryName::OnWriteDirManually(wxCommandEvent& event) void DirectoryName::OnDirSelected(wxFileDirPickerEvent& event) { const wxString newPath = event.GetPath(); - setDirectoryName(newPath, dirName_, NULL, *dirName_); + setDirectoryName(newPath, &dirName_, NULL, dirName_, staticBox_); event.Skip(); } @@ -271,11 +272,11 @@ void DirectoryName::OnDirSelected(wxFileDirPickerEvent& event) Zstring DirectoryName::getName() const { - return wxToZ(dirName_->GetValue()); + return wxToZ(dirName_.GetValue()); } void DirectoryName::setName(const Zstring& dirname) { - setDirectoryName(zToWx(dirname), dirName_, dirPicker_, *dirName_); + setDirectoryName(zToWx(dirname), &dirName_, &dirPicker_, dirName_, staticBox_); } diff --git a/shared/dir_name.h b/shared/dir_name.h index ad0f2a52..387f5668 100644 --- a/shared/dir_name.h +++ b/shared/dir_name.h @@ -15,7 +15,7 @@ #include "zstring.h" -class FFSFileDropEvent; +struct FFSFileDropEvent; class wxCommandEvent; class wxFileDirPickerEvent; @@ -26,11 +26,11 @@ namespace ffs3 class DirectoryNameMainDlg : private wxEvtHandler { public: - DirectoryNameMainDlg(wxWindow* dropWindow1, - wxWindow* dropWindow2, - wxDirPickerCtrl* dirPicker, - wxComboBox* dirName, - wxStaticBoxSizer* staticBox); + DirectoryNameMainDlg(wxWindow& dropWindow1, + wxWindow& dropWindow2, + wxDirPickerCtrl& dirPicker, + wxComboBox& dirName, + wxStaticBoxSizer& staticBox); virtual ~DirectoryNameMainDlg() {} @@ -44,20 +44,21 @@ private: void OnWriteDirManually(wxCommandEvent& event); void OnDirSelected(wxFileDirPickerEvent& event); - const wxWindow* dropWindow1_; - const wxWindow* dropWindow2_; - wxDirPickerCtrl* dirPicker_; - wxComboBox* dirName_; - wxStaticBoxSizer* staticBox_; + const wxWindow& dropWindow1_; + const wxWindow& dropWindow2_; + wxDirPickerCtrl& dirPicker_; + wxComboBox& dirName_; + wxStaticBoxSizer& staticBox_; }; class DirectoryName: private wxEvtHandler { public: - DirectoryName(wxWindow* dropWindow, - wxDirPickerCtrl* dirPicker, - wxTextCtrl* dirName); + DirectoryName(wxWindow& dropWindow, + wxDirPickerCtrl& dirPicker, + wxTextCtrl& dirName, + wxStaticBoxSizer* staticBox = NULL); //optional Zstring getName() const; void setName(const Zstring& dirname); @@ -67,9 +68,10 @@ private: void OnWriteDirManually(wxCommandEvent& event); void OnDirSelected(wxFileDirPickerEvent& event); - const wxWindow* dropWindow_; - wxDirPickerCtrl* dirPicker_; - wxTextCtrl* dirName_; + const wxWindow& dropWindow_; + wxDirPickerCtrl& dirPicker_; + wxTextCtrl& dirName_; + wxStaticBoxSizer* staticBox_; //optional }; } diff --git a/shared/dir_picker_i18n.h b/shared/dir_picker_i18n.h new file mode 100644 index 00000000..89b54bf4 --- /dev/null +++ b/shared/dir_picker_i18n.h @@ -0,0 +1,30 @@ +#ifndef DIR_PICKER_I18N_H_INCLUDED +#define DIR_PICKER_I18N_H_INCLUDED + +#include <wx/filepicker.h> +#include "i18n.h" + + +class FfsDirPickerCtrl : public wxDirPickerCtrl +{ +public: + FfsDirPickerCtrl(wxWindow* parent, wxWindowID id, + const wxString& path = wxEmptyString, + const wxString& message = wxDirSelectorPromptStr, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxDIRP_DEFAULT_STYLE, + const wxValidator& validator = wxDefaultValidator, + const wxString& name = wxDirPickerCtrlNameStr) : + wxDirPickerCtrl(parent, id, path, message, pos, size, style, validator, name) + { +#ifdef FFS_WIN + //fix wxWidgets localization gap: + wxButton* button = dynamic_cast<wxButton*>(m_pickerIface); + if (button) button->SetLabel(_("Browse")); +#endif + } +}; + + +#endif // DIR_PICKER_I18N_H_INCLUDED diff --git a/shared/dst_hack.cpp b/shared/dst_hack.cpp index a2841ce0..5ec58e06 100644 --- a/shared/dst_hack.cpp +++ b/shared/dst_hack.cpp @@ -1,6 +1,6 @@ #include "dst_hack.h" #include "system_constants.h" -#include <wx/intl.h> +#include "i18n.h" #include "long_path_prefix.h" #include "string_conv.h" #include "system_func.h" @@ -10,51 +10,63 @@ #include "global_func.h" #include <limits> +using namespace ffs3; -bool dst::isFatDrive(const Zstring& fileName) //throw() -{ - using namespace ffs3; - - const size_t BUFFER_SIZE = MAX_PATH + 1; - wchar_t buffer[BUFFER_SIZE]; +namespace +{ +//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points) +Zstring getVolumeName(const Zstring& filename) +{ //this call is expensive: ~1.5 ms! - // if (!::GetVolumePathName(applyLongPathPrefix(fileName).c_str(), //__in LPCTSTR lpszFileName, - // buffer, //__out LPTSTR lpszVolumePathName, + // if (!::GetVolumePathName(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpszFileName, + // fsName, //__out LPTSTR lpszVolumePathName, // BUFFER_SIZE)) //__in DWORD cchBufferLength // ... - // Zstring volumePath = buffer; + // Zstring volumePath = fsName; // if (!volumePath.EndsWith(common::FILE_NAME_SEPARATOR)) //a trailing backslash is required // volumePath += common::FILE_NAME_SEPARATOR; - //fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points) - Zstring volumePath; - { - Zstring nameFmt = removeLongPathPrefix(fileName); //throw() - if (!nameFmt.EndsWith(Zstr("\\"))) - nameFmt += Zstr("\\"); //GetVolumeInformation expects trailing backslash + Zstring nameFmt = removeLongPathPrefix(filename); //throw() + if (!nameFmt.EndsWith(Zstr("\\"))) + nameFmt += Zstr("\\"); //GetVolumeInformation expects trailing backslash - if (nameFmt.StartsWith(Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\" - { - size_t nameSize = nameFmt.size(); - const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2); - if (posFirstSlash != Zstring::npos) - { - nameSize = posFirstSlash + 1; - const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1); - if (posSecondSlash != Zstring::npos) - nameSize = posSecondSlash + 1; - } - volumePath = Zstring(nameFmt.c_str(), nameSize); //include trailing backslash! - } - else //local path: "C:\Folder\" + if (nameFmt.StartsWith(Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\" + { + size_t nameSize = nameFmt.size(); + const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2); + if (posFirstSlash != Zstring::npos) { - const size_t pos = nameFmt.find(Zstr(":\\")); - if (pos != 1) //expect single letter volume - return false; - volumePath = Zstring(nameFmt.c_str(), 3); + nameSize = posFirstSlash + 1; + const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1); + if (posSecondSlash != Zstring::npos) + nameSize = posSecondSlash + 1; } + return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash! + } + else //local path: "C:\Folder\" + { + const size_t pos = nameFmt.find(Zstr(":\\")); + if (pos == 1) //expect single letter volume + return Zstring(nameFmt.c_str(), 3); } + + return Zstring(); +} +} + + +bool dst::isFatDrive(const Zstring& fileName) //throw() +{ + using namespace ffs3; + + const size_t BUFFER_SIZE = MAX_PATH + 1; + wchar_t fsName[BUFFER_SIZE]; + + const Zstring volumePath = getVolumeName(fileName); + if (volumePath.empty()) + return false; + //suprisingly fast: ca. 0.03 ms per call! if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, NULL, //__out LPTSTR lpVolumeNameBuffer, @@ -62,13 +74,13 @@ bool dst::isFatDrive(const Zstring& fileName) //throw() NULL, //__out_opt LPDWORD lpVolumeSerialNumber, NULL, //__out_opt LPDWORD lpMaximumComponentLength, NULL, //__out_opt LPDWORD lpFileSystemFlags, - buffer, //__out LPTSTR lpFileSystemNameBuffer, + fsName, //__out LPTSTR lpFileSystemNameBuffer, BUFFER_SIZE)) //__in DWORD nFileSystemNameSize { assert(false); //shouldn't happen return false; } - const Zstring fileSystem = buffer; + const Zstring fileSystem = fsName; //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised) return fileSystem == Zstr("FAT") || diff --git a/shared/file_error.h b/shared/file_error.h index 152062af..e8497a92 100644 --- a/shared/file_error.h +++ b/shared/file_error.h @@ -15,9 +15,7 @@ namespace ffs3 class FileError //Exception base class used to notify file/directory copy/delete errors { public: - FileError(const wxString& message) : - errorMessage(message) {} - + FileError(const wxString& message) : errorMessage(message) {} virtual ~FileError() {} const wxString& msg() const @@ -29,12 +27,12 @@ private: const wxString errorMessage; }; +#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const wxString& message) : FileError(message) {} }; -class ErrorNotExisting : public FileError -{ -public: - ErrorNotExisting(const wxString& message) : FileError(message) {} -}; +DEFINE_NEW_FILE_ERROR(ErrorNotExisting); +DEFINE_NEW_FILE_ERROR(ErrorTargetExisting); +DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); +DEFINE_NEW_FILE_ERROR(ErrorFileLocked); } #endif // FILEERROR_H_INCLUDED diff --git a/shared/file_handling.cpp b/shared/file_handling.cpp index 14a4b84c..97c4d077 100644 --- a/shared/file_handling.cpp +++ b/shared/file_handling.cpp @@ -5,22 +5,21 @@ // ************************************************************************** // #include "file_handling.h" -#include <wx/intl.h> +#include <map> +#include <algorithm> +#include <boost/scoped_array.hpp> +#include <boost/bind.hpp> +#include <stdexcept> #include "system_func.h" #include "global_func.h" #include "system_constants.h" #include "file_traverser.h" -#include <boost/bind.hpp> -#include <algorithm> -#include <wx/datetime.h> #include "string_conv.h" -#include <wx/utils.h> -#include <boost/scoped_array.hpp> -#include <stdexcept> #include "loki/TypeManip.h" #include "loki/ScopeGuard.h" -#include <map> #include "symlink_target.h" +#include "file_io.h" +#include "i18n.h" #ifdef FFS_WIN #include "privilege.h" @@ -32,7 +31,6 @@ #elif defined FFS_LINUX #include <sys/stat.h> -#include "file_io.h" #include <time.h> #include <utime.h> #include <cerrno> @@ -44,162 +42,7 @@ #endif using ffs3::FileError; - - -namespace -{ -#ifdef FFS_WIN -Zstring resolveRelativePath(const Zstring& relativeName, DWORD proposedBufferSize = 1000) -{ - boost::scoped_array<Zchar> fullPath(new Zchar[proposedBufferSize]); - const DWORD rv = ::GetFullPathName( - relativeName.c_str(), //__in LPCTSTR lpFileName, - proposedBufferSize, //__in DWORD nBufferLength, - fullPath.get(), //__out LPTSTR lpBuffer, - NULL); //__out LPTSTR *lpFilePart - if (rv == 0 || rv == proposedBufferSize) - //ERROR! Don't do anything - return relativeName; - if (rv > proposedBufferSize) - return resolveRelativePath(relativeName, rv); - - return fullPath.get(); -} - -#elif defined FFS_LINUX -Zstring resolveRelativePath(const Zstring& relativeName) //additional: resolves symbolic links!!! -{ - char absolutePath[PATH_MAX + 1]; - if (::realpath(relativeName.c_str(), absolutePath) == NULL) - //ERROR! Don't do anything - return relativeName; - - return Zstring(absolutePath); -} -#endif - - -bool replaceMacro(wxString& macro) //macro without %-characters, return true if replaced successfully -{ - if (macro.IsEmpty()) - return false; - - //there are equally named environment variables %TIME%, %DATE% existing, so replace these first! - if (macro.CmpNoCase(wxT("time")) == 0) - { - macro = wxDateTime::Now().FormatISOTime(); - macro.Replace(wxT(":"), wxT("")); - return true; - } - - if (macro.CmpNoCase(wxT("date")) == 0) - { - macro = wxDateTime::Now().FormatISODate(); - return true; - } - - if (macro.CmpNoCase(wxT("month")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%B")); - return true; - } - - if (macro.CmpNoCase(wxT("week")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%U")); - return true; - } - - if (macro.CmpNoCase(wxT("year")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%Y")); - return true; - } - - //try to apply environment variables - wxString envValue; - if (wxGetEnv(macro, &envValue)) - { - macro = envValue; - - //some postprocessing: - macro.Trim(true); //remove leading, trailing blanks - macro.Trim(false); // - - //remove leading, trailing double-quotes - if (macro.StartsWith(wxT("\"")) && - macro.EndsWith(wxT("\"")) && - macro.length() >= 2) - macro = wxString(macro.c_str() + 1, macro.length() - 2); - return true; - } - - return false; -} - - -void expandMacros(wxString& text) -{ - const wxChar SEPARATOR = '%'; - - if (text.Find(SEPARATOR) != wxNOT_FOUND) - { - wxString prefix = text.BeforeFirst(SEPARATOR); - wxString postfix = text.AfterFirst(SEPARATOR); - if (postfix.Find(SEPARATOR) != wxNOT_FOUND) - { - wxString potentialMacro = postfix.BeforeFirst(SEPARATOR); - wxString rest = postfix.AfterFirst(SEPARATOR); //text == prefix + SEPARATOR + potentialMacro + SEPARATOR + rest - - if (replaceMacro(potentialMacro)) - { - expandMacros(rest); - text = prefix + potentialMacro + rest; - } - else - { - rest = SEPARATOR + rest; - expandMacros(rest); - text = prefix + SEPARATOR + potentialMacro + rest; - } - } - } -} -} - - -Zstring ffs3::getFormattedDirectoryName(const Zstring& dirname) -{ - //Formatting is needed since functions expect the directory to end with '\' to be able to split the relative names. - //note: don't combine directory formatting with wxFileName, as it doesn't respect //?/ - prefix! - - wxString dirnameTmp = zToWx(dirname); - expandMacros(dirnameTmp); - - Zstring output = wxToZ(dirnameTmp); - - output.Trim(); - - if (output.empty()) //an empty string will later be returned as "\"; this is not desired - return Zstring(); - - /* - resolve relative names; required by: - WINDOWS: - - \\?\-prefix which needs absolute names - - Volume Shadow Copy: volume name needs to be part of each filename - - file icon buffer (at least for extensions that are actually read from disk, e.g. "exe") - - ::SHFileOperation(): Using relative path names is not thread safe - WINDOWS/LINUX: - - detection of dependent directories, e.g. "\" and "C:\test" - */ - output = resolveRelativePath(output); - - if (!output.EndsWith(common::FILE_NAME_SEPARATOR)) - output += common::FILE_NAME_SEPARATOR; - - return output; -} +using namespace ffs3; bool ffs3::fileExists(const Zstring& filename) @@ -275,18 +118,22 @@ wxULongLong getFileSizeSymlink(const Zstring& linkName) //throw (FileError) OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (hFile != INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) { - Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFile); - (void)dummy; //silence warning "unused variable" + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFile); + (void)dummy; //silence warning "unused variable" - BY_HANDLE_FILE_INFORMATION fileInfoByHandle; - if (::GetFileInformationByHandle(hFile, &fileInfoByHandle)) - return wxULongLong(fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow); + BY_HANDLE_FILE_INFORMATION fileInfoByHandle = {}; + if (!::GetFileInformationByHandle(hFile, &fileInfoByHandle)) + { + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + return wxULongLong(fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow); } } #endif @@ -331,7 +178,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! const size_t bufferSize = std::max(pathName.size(), static_cast<size_t>(10000)); boost::scoped_array<wchar_t> buffer(new wchar_t[bufferSize]); - //pathName need not exist! + //full pathName need not yet exist! if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, buffer.get(), //__out LPTSTR lpszVolumePathName, static_cast<DWORD>(bufferSize))) //__in DWORD cchBufferLength @@ -364,8 +211,8 @@ dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! if (volumePathName.size() > 1 && volumePathName.EndsWith(common::FILE_NAME_SEPARATOR)) //exception: allow '/' volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); - struct stat fileInfo; - while (::lstat(volumePathName.c_str(), &fileInfo) != 0) + struct stat fileInfo = {}; + while (::lstat(volumePathName.c_str(), &fileInfo) != 0) //go up in folder hierarchy until existing folder is found { volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); //returns empty string if ch not found if (volumePathName.empty()) @@ -396,48 +243,43 @@ ffs3::ResponseSameVol ffs3::onSameVolume(const Zstring& folderLeft, const Zstrin void ffs3::removeFile(const Zstring& filename) //throw (FileError); { - //no error situation if file is not existing! manual deletion relies on it! - if (!somethingExists(filename)) - return; //neither file nor any other object (e.g. broken symlink) with that name existing - #ifdef FFS_WIN - const Zstring filenameFmt = applyLongPathPrefix(filename); - //remove file, support for \\?\-prefix + const Zstring filenameFmt = applyLongPathPrefix(filename); if (!::DeleteFile(filenameFmt.c_str())) +#elif defined FFS_LINUX + if (::unlink(filename.c_str()) != 0) +#endif { - //optimization: change file attributes ONLY when necessary! +#ifdef FFS_WIN + //perf: apply ONLY when necessary! if (::GetLastError() == ERROR_ACCESS_DENIED) //function fails if file is read-only { - //initialize file attributes - if (::SetFileAttributes(filenameFmt.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL)) //attributes to set - { - //now try again... - if (::DeleteFile(filenameFmt.c_str())) - return; - } + //(try to) normalize file attributes + ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL); + + //now try again... + if (::DeleteFile(filenameFmt.c_str())) + return; } +#endif + //eval error code before next call + const wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted(); - wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } -#elif defined FFS_LINUX - if (::unlink(filename.c_str()) != 0) - { - wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + //no error situation if file is not existing! manual deletion relies on it! + //perf: place check in error handling block + //warning: this call changes error code!! + if (!somethingExists(filename)) + return; //neither file nor any other object (e.g. broken symlink) with that name existing + + throw FileError(errorMessage); } -#endif } namespace { -struct ErrorDifferentVolume : public ffs3::FileError -{ - ErrorDifferentVolume(const wxString& message) : FileError(message) {} -}; +DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); /* Usage overview: @@ -448,7 +290,7 @@ struct ErrorDifferentVolume : public ffs3::FileError */ //wrapper for file system rename function: //throw (FileError); ErrorDifferentVolume if it is due to moving file to another volume -void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw (FileError, ErrorDifferentVolume) +void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting) { using namespace ffs3; //for zToWx() @@ -465,8 +307,7 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw const DWORD oldNameAttrib = ::GetFileAttributes(oldNameFmt.c_str()); if (oldNameAttrib != INVALID_FILE_ATTRIBUTES) { - if (::SetFileAttributes(oldNameFmt.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute + if (::SetFileAttributes(oldNameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute { //try again... if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, @@ -474,8 +315,7 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw 0)) //__in DWORD dwFlags { //(try to) restore file attributes - ::SetFileAttributes(newNameFmt.c_str(), //don't handle error - oldNameAttrib); + ::SetFileAttributes(newNameFmt.c_str(), oldNameAttrib); //don't handle error return; } else @@ -491,22 +331,27 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw } } + const DWORD lastError = ::GetLastError(); const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(); - if (::GetLastError() == ERROR_NOT_SAME_DEVICE) + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMessage); + else if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); else throw FileError(errorMessage); } #elif defined FFS_LINUX - //rename temporary file if (::rename(oldName.c_str(), newName.c_str()) != 0) { + const int lastError = errno; const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(); - if (errno == EXDEV) + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == EXDEV) throw ErrorDifferentVolume(errorMessage); + else if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); else throw FileError(errorMessage); } @@ -572,11 +417,11 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw { const Zstring fileNameOrig = newName.AfterLast(common::FILE_NAME_SEPARATOR); //returns the whole string if ch not found const Zstring fileNameShort = getFilenameFmt(newName, ::GetShortPathName).AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error - const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName).AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error + const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName) .AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error if (!fileNameShort.empty() && !fileNameLong.empty() && - EqualFilename()(fileNameOrig, fileNameShort) && + EqualFilename() (fileNameOrig, fileNameShort) && !EqualFilename()(fileNameShort, fileNameLong)) { //we detected an event where newName is in shortname format (although it is intended to be a long name) and @@ -589,7 +434,7 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw const Zstring parkedTarget = createTemp8Dot3Name(newName); //move already existing short name out of the way for now - renameFileInternal(unrelatedPathLong, parkedTarget); //throw (FileError, ErrorDifferentVolume); + renameFileInternal(unrelatedPathLong, parkedTarget); //throw (FileError: ErrorDifferentVolume); //DON'T call ffs3::renameFile() to avoid reentrance! //schedule cleanup; the file system should assign this unrelated file a new (unique) short name @@ -607,11 +452,11 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw //rename file: no copying!!! -void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError, ErrorDifferentVolume); +void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting); { try { - renameFileInternal(oldName, newName); //throw (FileError, ErrorDifferentVolume) + renameFileInternal(oldName, newName); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting) } catch (const FileError&) { @@ -624,80 +469,77 @@ void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (F } -using ffs3::MoveFileCallback; +using ffs3::CallbackMoveFile; -class CopyCallbackImpl : public ffs3::CopyFileCallback //callback functionality +class CopyCallbackImpl : public ffs3::CallbackCopyFile //callback functionality { public: - CopyCallbackImpl(const Zstring& sourceFile, MoveFileCallback& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + + virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); } virtual Response updateCopyStatus(const wxULongLong& totalBytesTransferred) { switch (moveCallback.requestUiRefresh(sourceFile_)) { - case MoveFileCallback::CONTINUE: - return CopyFileCallback::CONTINUE; + case CallbackMoveFile::CONTINUE: + return CallbackCopyFile::CONTINUE; - case MoveFileCallback::CANCEL: - return CopyFileCallback::CANCEL; + case CallbackMoveFile::CANCEL: + return CallbackCopyFile::CANCEL; } - return CopyFileCallback::CONTINUE; //dummy return value + return CallbackCopyFile::CONTINUE; //dummy return value } private: const Zstring sourceFile_; - MoveFileCallback& moveCallback; + CallbackMoveFile& moveCallback; }; -void ffs3::moveFile(const Zstring& sourceFile, const Zstring& targetFile, MoveFileCallback* callback) //throw (FileError); +void ffs3::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { //call back once per file (moveFile() is called by moveDirectory()) if (callback) switch (callback->requestUiRefresh(sourceFile)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } - //support case-sensitive renaming - if (EqualFilename()(sourceFile, targetFile)) //difference in case only - return renameFile(sourceFile, targetFile); //throw (FileError, ErrorDifferentVolume); + const bool targetExisting = fileExists(targetFile); - if (somethingExists(targetFile)) //test file existence: e.g. Linux might silently overwrite existing symlinks + if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks throw FileError(wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"") + wxT("\n\n") + _("Target file already existing!")); - //moving of symbolic links should work correctly: - - //first try to move the file directly without copying - try + if (!targetExisting) { - renameFile(sourceFile, targetFile); //throw (FileError, ErrorDifferentVolume); - return; - } - //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) - catch (const ErrorDifferentVolume&) {} - + //try to move the file directly without copying + try + { + renameFile(sourceFile, targetFile); //throw (FileError: ErrorDifferentVolume); + return; //great, we get away cheaply! + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) + catch (const ErrorDifferentVolume&) {} - //file is on a different volume: let's copy it - std::auto_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); + //file is on a different volume: let's copy it + std::auto_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); - copyFile(sourceFile, - targetFile, - true, //copy symbolic links - false, //dont copy filesystem permissions -#ifdef FFS_WIN - NULL, //supply handler for making shadow copies -#endif - copyCallback.get()); //throw (FileError); + if (symlinkExists(sourceFile)) + copySymlink(sourceFile, targetFile, SYMLINK_TYPE_FILE, false); //throw (FileError) dont copy filesystem permissions + else + copyFile(sourceFile, targetFile, false, copyCallback.get()); //throw (FileError); - //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! + //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! + } - removeFile(sourceFile); + removeFile(sourceFile); //throw (FileError) + //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine } namespace @@ -705,45 +547,48 @@ namespace class TraverseOneLevel : public ffs3::TraverseCallback { public: - typedef std::vector<std::pair<Zstring, Zstring> > NamePair; + typedef std::pair<Zstring, Zstring> NamePair; + typedef std::vector<NamePair> NameList; - TraverseOneLevel(NamePair& filesShort, NamePair& dirsShort) : - m_files(filesShort), - m_dirs(dirsShort) {} + TraverseOneLevel(NameList& files, NameList& dirs) : + files_(files), + dirs_(dirs) {} virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) { - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); + files_.push_back(NamePair(shortName, fullName)); } + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { if (details.dirLink) - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); + dirs_.push_back(NamePair(shortName, fullName)); else - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); + files_.push_back(NamePair(shortName, fullName)); } virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) { - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); + dirs_.push_back(NamePair(shortName, fullName)); return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; moveDirectory works recursively! } + virtual void onError(const wxString& errorText) { throw FileError(errorText); } private: - NamePair& m_files; - NamePair& m_dirs; + NameList& files_; + NameList& dirs_; }; -struct RemoveCallbackImpl : public ffs3::RemoveDirCallback +struct RemoveCallbackImpl : public ffs3::CallbackRemoveDir { RemoveCallbackImpl(const Zstring& sourceDir, const Zstring& targetDir, - MoveFileCallback& moveCallback) : + CallbackMoveFile& moveCallback) : sourceDir_(sourceDir), targetDir_(targetDir), moveCallback_(moveCallback) {} @@ -752,9 +597,9 @@ struct RemoveCallbackImpl : public ffs3::RemoveDirCallback { switch (moveCallback_.requestUiRefresh(sourceDir_)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw ffs3::FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + ffs3::zToWx(sourceDir_) + wxT("\" ->\n\"") + ffs3::zToWx(targetDir_) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } @@ -763,12 +608,12 @@ struct RemoveCallbackImpl : public ffs3::RemoveDirCallback private: const Zstring sourceDir_; const Zstring targetDir_; - MoveFileCallback& moveCallback_; + CallbackMoveFile& moveCallback_; }; } -void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); +void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { using namespace ffs3; @@ -776,63 +621,63 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool if (callback) switch (callback->requestUiRefresh(sourceDir)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } - //handle symbolic links - if (symlinkExists(sourceDir)) - { - createDirectory(targetDir, sourceDir, true, false); //copy symbolic link, don't copy permissions - removeDirectory(sourceDir, NULL); //if target is already another symlink or directory, sourceDir-symlink is silently deleted - return; - } + const bool targetExisting = dirExists(targetDir); - if (somethingExists(targetDir)) - { - if (!ignoreExistingDirs) //directory or symlink exists (or even a file... this error will be caught later) - throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + - wxT("\n\n") + _("Target directory already existing!")); - } - else + if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later) + throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + + wxT("\n\n") + _("Target directory already existing!")); + + const bool isSymlink = symlinkExists(sourceDir); + + if (!targetExisting) { //first try to move the directory directly without copying try { - renameFile(sourceDir, targetDir); //throw (FileError, ErrorDifferentVolume); - return; + renameFile(sourceDir, targetDir); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting); + return; //great, we get away cheaply! } //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) catch (const ErrorDifferentVolume&) {} //create target - createDirectory(targetDir, sourceDir, false, false); //throw (FileError); don't copy permissions + if (isSymlink) + copySymlink(sourceDir, targetDir, SYMLINK_TYPE_DIR, false); //throw (FileError) -> don't copy permissions + else + createDirectory(targetDir, sourceDir, false); //throw (FileError) } - //move files/folders recursively - TraverseOneLevel::NamePair fileList; //list of names: 1. short 2.long - TraverseOneLevel::NamePair dirList; // + if (!isSymlink) //handle symbolic links + { + //move files/folders recursively + TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long + TraverseOneLevel::NameList dirList; // - //traverse source directory one level - TraverseOneLevel traverseCallback(fileList, dirList); - traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks + //traverse source directory one level + TraverseOneLevel traverseCallback(fileList, dirList); + traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks - const Zstring targetDirFormatted = targetDir.EndsWith(common::FILE_NAME_SEPARATOR) ? //ends with path separator - targetDir : - targetDir + common::FILE_NAME_SEPARATOR; + const Zstring targetDirFormatted = targetDir.EndsWith(common::FILE_NAME_SEPARATOR) ? //ends with path separator + targetDir : + targetDir + common::FILE_NAME_SEPARATOR; - //move files - for (TraverseOneLevel::NamePair::const_iterator i = fileList.begin(); i != fileList.end(); ++i) - ffs3::moveFile(i->second, targetDirFormatted + i->first, callback); + //move files + for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i) + ffs3::moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw (FileError: ErrorTargetExisting); - //move directories - for (TraverseOneLevel::NamePair::const_iterator i = dirList.begin(); i != dirList.end(); ++i) - ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, true, callback); + //move directories + for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i) + ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback); - //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + } //delete source std::auto_ptr<RemoveCallbackImpl> removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, targetDir, *callback) : NULL); @@ -840,7 +685,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool } -void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); +void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { #ifdef FFS_WIN const Zstring& sourceDirFormatted = sourceDir; @@ -857,7 +702,7 @@ void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, boo targetDir; #endif - ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExistingDirs, callback); + ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback); } @@ -895,7 +740,7 @@ private: }; -void ffs3::removeDirectory(const Zstring& directory, RemoveDirCallback* callback) +void ffs3::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) { //no error situation if directory is not existing! manual deletion relies on it! if (!somethingExists(directory)) @@ -904,17 +749,10 @@ void ffs3::removeDirectory(const Zstring& directory, RemoveDirCallback* callback #ifdef FFS_WIN const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix - //initialize file attributes - if (!::SetFileAttributes( // initialize file attributes: actually NEEDED for symbolic links also! - directoryFmt.c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + //(try to) normalize file attributes: actually NEEDED for symbolic links also! + ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL); #endif - //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! if (symlinkExists(directory)) //remove symlink directly { @@ -970,7 +808,6 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo { #ifdef FFS_WIN FILETIME creationTime = {}; - FILETIME lastAccessTime = {}; FILETIME lastWriteTime = {}; { @@ -1006,7 +843,7 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo if (!::GetFileTime(hSource, //__in HANDLE hFile, &creationTime, //__out_opt LPFILETIME lpCreationTime, - &lastAccessTime, //__out_opt LPFILETIME lpLastAccessTime, + NULL, //__out_opt LPFILETIME lpLastAccessTime, &lastWriteTime)) //__out_opt LPFILETIME lpLastWriteTime { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); @@ -1016,7 +853,6 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo else { creationTime = sourceAttr.ftCreationTime; - lastAccessTime = sourceAttr.ftLastAccessTime; lastWriteTime = sourceAttr.ftLastWriteTime; } @@ -1065,7 +901,7 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo if (!::SetFileTime(hTarget, &creationTime, - &lastAccessTime, + NULL, &lastWriteTime)) { wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); @@ -1088,14 +924,14 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo #elif defined FFS_LINUX if (deRefSymlinks) { - struct stat objInfo; + struct stat objInfo = {}; if (::stat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - struct utimbuf newTimes; + struct utimbuf newTimes = {}; newTimes.actime = objInfo.st_atime; newTimes.modtime = objInfo.st_mtime; @@ -1108,14 +944,14 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo } else { - struct stat objInfo; + struct stat objInfo = {}; if (::lstat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - struct timeval newTimes[2]; + struct timeval newTimes[2] = {}; newTimes[0].tv_sec = objInfo.st_atime; /* seconds */ newTimes[0].tv_usec = 0; /* microseconds */ @@ -1207,75 +1043,10 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa } #endif -enum SymlinkType -{ - SYMLINK_TYPE_FILE, - SYMLINK_TYPE_DIR -}; -void copySymlinkInternal(const Zstring& sourceLink, const Zstring& targetLink, SymlinkType type, bool copyFilePermissions) //throw (FileError) -{ - using namespace ffs3; - -#ifdef FFS_WIN - const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw (FileError) - - //dynamically load windows API function - typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)( - LPCTSTR lpSymlinkFileName, - LPCTSTR lpTargetFileName, - DWORD dwFlags); - static const CreateSymbolicLinkFunc createSymbolicLink = util::getDllFun<CreateSymbolicLinkFunc>(L"kernel32.dll", "CreateSymbolicLinkW"); - if (createSymbolicLink == NULL) - throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("CreateSymbolicLinkW") + wxT("\"")); - - if (!createSymbolicLink( //seems no long path prefix is required... - targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - linkPath.c_str(), //__in LPTSTR lpTargetFileName, - (type == SYMLINK_TYPE_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags - { - const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } - -#elif defined FFS_LINUX - //copy symbolic link - const int BUFFER_SIZE = 10000; - char buffer[BUFFER_SIZE]; - const int bytesWritten = ::readlink(sourceLink.c_str(), buffer, BUFFER_SIZE); - if (bytesWritten < 0 || bytesWritten == BUFFER_SIZE) - { - wxString errorMessage = wxString(_("Error resolving symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\""); - if (bytesWritten < 0) errorMessage += wxString(wxT("\n\n")) + ffs3::getLastErrorFormatted(); - throw FileError(errorMessage); - } - //set null-terminating char - buffer[bytesWritten] = 0; - - if (::symlink(buffer, targetLink.c_str()) != 0) - { - const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } -#endif - - //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist - Loki::ScopeGuard guardNewDir = type == SYMLINK_TYPE_DIR ? - Loki::MakeGuard(&TryCleanUp::tryDeleteDir, targetLink) : - Loki::MakeGuard(&TryCleanUp::tryDeleteFile, targetLink); - - copyFileTimes(sourceLink, targetLink, false); //throw (FileError) - - if (copyFilePermissions) - copyObjectPermissions(sourceLink, targetLink, false); //throw FileError() - - guardNewDir.Dismiss(); //target has been created successfully! -} -} - #ifdef HAVE_SELINUX //copy SELinux security context -void copySecurityContext(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw FileError() +void copySecurityContext(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError) { using ffs3::zToWx; @@ -1329,7 +1100,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, bool dere //copy permissions for files, directories or symbolic links: requires admin rights -void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw FileError(); +void copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError); { #ifdef FFS_WIN //setting privileges requires admin rights! @@ -1431,13 +1202,13 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b #elif defined FFS_LINUX #ifdef HAVE_SELINUX //copy SELinux security context - copySecurityContext(source, target, derefSymlinks); //throw FileError() + copySecurityContext(source, target, derefSymlinks); //throw (FileError) #endif if (derefSymlinks) { struct stat fileInfo; - if (::stat(source.c_str(), &fileInfo) != 0 || + if (::stat (source.c_str(), &fileInfo) != 0 || ::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! ::chmod(target.c_str(), fileInfo.st_mode) != 0) { @@ -1448,7 +1219,7 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b else { struct stat fileInfo; - if (::lstat(source.c_str(), &fileInfo) != 0 || + if (::lstat (source.c_str(), &fileInfo) != 0 || ::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() { @@ -1460,7 +1231,7 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b } -void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions, int level) +void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level) { using namespace ffs3; @@ -1476,108 +1247,110 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat { //call function recursively const Zstring templateParent = templateDir.BeforeLast(common::FILE_NAME_SEPARATOR); - createDirectoryRecursively(dirParent, templateParent, false, copyFilePermissions, level + 1); //don't create symbolic links in recursion! + createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1); } //now creation should be possible - if (templateDir.empty() || !somethingExists(templateDir)) - { - //default directory creation + //default directory creation #ifdef FFS_WIN - if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL))// pointer to a directory path string + //don't use ::CreateDirectoryEx: + //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage + //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! + //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!) + if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL)) #elif defined FFS_LINUX - if (::mkdir(directory.c_str(), 0755) != 0) + if (::mkdir(directory.c_str(), 0755) != 0) #endif - { - if (level != 0) return; - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + { + if (level != 0) return; + wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - else + + if (!templateDir.empty()) { #ifdef FFS_WIN - //symbolic link handling - if (symlinkExists(templateDir)) + //try to copy file attributes + Zstring sourcePath; + + if (symlinkExists(templateDir)) //dereference symlink! { - if (copyDirectorySymLinks) - return copySymlinkInternal(templateDir, directory, SYMLINK_TYPE_DIR, copyFilePermissions); //throw (FileError) - else //create directory based on target of symbolic link + try { //get target directory of symbolic link - Zstring linkPath; - try - { - linkPath = resolveDirectorySymlink(templateDir); //throw (FileError) - } - catch (FileError&) - { - //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... - if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) - { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } - return; - } - - if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered - applyLongPathPrefix(linkPath).c_str(), // pointer to path string of template directory - applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) - { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + sourcePath = resolveDirectorySymlink(templateDir); //throw (FileError) } + catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... } else //no symbolic link + sourcePath = templateDir; + + //try to copy file attributes + if (!sourcePath.empty()) { - //automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! - //also it isn't able to copy most junctions because of missing permissions (although target path can be retrieved!) - if (!::CreateDirectoryEx( - applyLongPathPrefix(templateDir).c_str(), // pointer to path string of template directory - applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) + const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str()); + if (sourceAttr != INVALID_FILE_ATTRIBUTES) { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; + + ::SetFileAttributes(applyLongPathPrefix(directory), sourceAttr); + + if (isEncrypted) + ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) + + if (isCompressed) + { + HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hDir != INVALID_HANDLE_VALUE) + { + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hDir); + (void)dummy; //silence warning "unused variable" + + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + + DWORD bytesReturned = 0; + ::DeviceIoControl(hDir, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL); //OVERLAPPED structure + } + } } } +#endif -#elif defined FFS_LINUX - //symbolic link handling - if (copyDirectorySymLinks && - symlinkExists(templateDir)) - //there is no directory-type symlink in Linux! => just copy as file - return copySymlinkInternal(templateDir, directory, SYMLINK_TYPE_DIR, copyFilePermissions); //throw (FileError) - - //default directory creation - if (::mkdir(directory.c_str(), 0755) != 0) + //try to copy file times: NOT mission critical for a directory + try { - if (level != 0) return; - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + copyFileTimes(templateDir, directory, true); //throw (FileError) } -#endif - Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); //ensure cleanup: + catch (FileError&) {} - copyFileTimes(templateDir, directory, !copyDirectorySymLinks); //throw (FileError) + Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); //ensure cleanup: + //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) - copyObjectPermissions(templateDir, directory, !copyDirectorySymLinks); //throw FileError() + copyObjectPermissions(templateDir, directory, true); //throw (FileError) guardNewDir.Dismiss(); //target has been created successfully! } } +} -void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions) +void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) { //remove trailing separator const Zstring dirFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? @@ -1588,13 +1361,56 @@ void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, templateDir.BeforeLast(common::FILE_NAME_SEPARATOR) : templateDir; - createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, copyFilePermissions, 0); + createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0); } void ffs3::createDirectory(const Zstring& directory) { - ffs3::createDirectory(directory, Zstring(), false, false); + ffs3::createDirectory(directory, Zstring(), false); +} + + +void ffs3::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, ffs3::SymlinkType type, bool copyFilePermissions) //throw (FileError) +{ + const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw (FileError) + +#ifdef FFS_WIN + //dynamically load windows API function + typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); + static const CreateSymbolicLinkFunc createSymbolicLink = util::getDllFun<CreateSymbolicLinkFunc>(L"kernel32.dll", "CreateSymbolicLinkW"); + if (createSymbolicLink == NULL) + throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("CreateSymbolicLinkW") + wxT("\"")); + + if (!createSymbolicLink( //seems no long path prefix is required... + targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, + linkPath.c_str(), //__in LPTSTR lpTargetFileName, + (type == SYMLINK_TYPE_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags + { + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + +#elif defined FFS_LINUX + if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) + { + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } +#endif + + //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist + Loki::ScopeGuard guardNewDir = type == SYMLINK_TYPE_DIR ? + Loki::MakeGuard(&TryCleanUp::tryDeleteDir, targetLink) : + Loki::MakeGuard(&TryCleanUp::tryDeleteFile, targetLink); + + //file times: essential for a symlink: enforce this! (don't just try!) + copyFileTimes(sourceLink, targetLink, false); //throw (FileError) + + if (copyFilePermissions) + copyObjectPermissions(sourceLink, targetLink, false); //throw (FileError) + + guardNewDir.Dismiss(); //target has been created successfully! } @@ -1610,15 +1426,8 @@ Zstring createTempName(const Zstring& filename) return output; } -} #ifdef FFS_WIN -namespace -{ -#ifndef COPY_FILE_COPY_SYMLINK -#define COPY_FILE_COPY_SYMLINK 0x00000800 -#endif - DWORD CALLBACK copyCallbackInternal( LARGE_INTEGER totalFileSize, LARGE_INTEGER totalBytesTransferred, @@ -1630,11 +1439,11 @@ DWORD CALLBACK copyCallbackInternal( HANDLE hDestinationFile, LPVOID lpData) { - using ffs3::CopyFileCallback; + using ffs3::CallbackCopyFile; //small performance optimization: it seems this callback function is called for every 64 kB (depending on cluster size). static size_t callNr = 0; - if (++callNr % 4 == 0) //executing callback for each 256 kB should suffice + if (++callNr % 4 == 0) //executing callback every 256 kB should suffice { if (lpData != NULL) { @@ -1646,14 +1455,14 @@ DWORD CALLBACK copyCallbackInternal( This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu"), NULL, 0); - CopyFileCallback* callback = static_cast<CopyFileCallback*>(lpData); + CallbackCopyFile* callback = static_cast<CallbackCopyFile*>(lpData); try { switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) { - case CopyFileCallback::CONTINUE: + case CallbackCopyFile::CONTINUE: break; - case CopyFileCallback::CANCEL: + case CallbackCopyFile::CANCEL: return PROGRESS_CANCEL; } } @@ -1668,6 +1477,10 @@ DWORD CALLBACK copyCallbackInternal( } +#ifndef COPY_FILE_COPY_SYMLINK +#define COPY_FILE_COPY_SYMLINK 0x00000800 +#endif + bool supportForSymbolicLinks() { OSVERSIONINFO osvi = {}; @@ -1685,11 +1498,9 @@ bool supportForSymbolicLinks() #define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008 #endif - bool supportForNonEncryptedDestination() { - OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); //encrypted destination is not supported with Windows 2000 @@ -1699,39 +1510,28 @@ bool supportForNonEncryptedDestination() //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx return false; } -} -void ffs3::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - bool copyFilePermissions, - shadow::ShadowCopy* shadowCopyHandler, - ffs3::CopyFileCallback* callback) +void rawCopyWinApi(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) { - //ffs3::fileExists(targetFile) -> avoid this call, performance; - //if target exists (very unlikely, because sync-algorithm deletes it) renaming below will fail! + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just before starting copy! DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - //copy symbolic links instead of the files pointed at - static const bool symlinksSupported = supportForSymbolicLinks(); //only set "true" if supported by OS: else copying in Windows XP fails - if (copyFileSymLinks && symlinksSupported) - copyFlags |= COPY_FILE_COPY_SYMLINK; + // static const bool symlinksSupported = supportForSymbolicLinks(); //only set "true" if supported by OS: else copying in Windows XP fails + // if (copyFileSymLinks && symlinksSupported) + // copyFlags |= COPY_FILE_COPY_SYMLINK; //allow copying from encrypted to non-encrytped location static const bool nonEncSupported = supportForNonEncryptedDestination(); if (nonEncSupported) copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set - - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&ffs3::removeFile, temporary); - - - if (!::CopyFileEx( //same performance as CopyFile() + if (!::CopyFileEx( //same performance like CopyFile() applyLongPathPrefix(sourceFile).c_str(), - applyLongPathPrefix(temporary).c_str(), + applyLongPathPrefix(targetFile).c_str(), callback != NULL ? copyCallbackInternal : NULL, callback, NULL, @@ -1741,44 +1541,30 @@ void ffs3::copyFile(const Zstring& sourceFile, //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! - //if file is locked (try to) use Windows Volume Shadow Copy Service - if (shadowCopyHandler != NULL && - (lastError == ERROR_SHARING_VIOLATION || - lastError == ERROR_LOCK_VIOLATION)) - { - //shadowFilename already contains prefix: E.g. "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Program Files\FFS\sample.dat" + //assemble error message... + wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); - Zstring shadowFilename; - try - { - shadowFilename = shadowCopyHandler->makeShadowCopy(sourceFile); //throw (FileError) - } - catch (const FileError& e) - { - wxString errorMsg = _("Error copying locked file %x!"); - errorMsg.Replace(wxT("%x"), wxString(wxT("\"")) + zToWx(sourceFile) + wxT("\"")); - errorMsg += wxT("\n\n") + e.msg(); - throw FileError(errorMsg); - } + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + throw ErrorFileLocked(errorMessage); - return copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied - targetFile, - copyFileSymLinks, - copyFilePermissions, - NULL, - callback); + if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!! + { + guardTarget.Dismiss(); //don't delete file that existed previously! + throw ErrorTargetExisting(errorMessage); } - //assemble error message... - wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); try //add more meaningful message { //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!) if (lastError == ERROR_INVALID_PARAMETER && dst::isFatDrive(targetFile) && - getFilesize(sourceFile) >= (wxULongLong(1024 * 1024 * 1024)*=4)) //throw (FileError) + getFilesize(sourceFile) >= wxULongLong(1024 * 1024 * 1024) * 4) //throw (FileError) errorMessage += wxT("\nFAT volume cannot store file larger than 4 gigabyte!"); } catch(...) {} @@ -1786,69 +1572,283 @@ void ffs3::copyFile(const Zstring& sourceFile, throw FileError(errorMessage); } - //rename temporary file: do not add anything else here (note specific error handing) - ffs3::renameFile(temporary, targetFile); - - guardTempFile.Dismiss(); //no need to delete temp file anymore - - Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&ffs3::removeFile, targetFile); - - //copy creation date (last modification date is REDUNDANTLY written, too, even for symlinks) - copyFileTimes(sourceFile, targetFile, !copyFileSymLinks); //throw (FileError) - - if (copyFilePermissions) - copyObjectPermissions(sourceFile, targetFile, !copyFileSymLinks); //throw FileError() + //adapt file modification time: + copyFileTimes(sourceFile, targetFile, true); //throw (FileError) - guardTargetFile.Dismiss(); + guardTarget.Dismiss(); //target has been created successfully! } -#elif defined FFS_LINUX -void ffs3::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - bool copyFilePermissions, - CopyFileCallback* callback) +void rawCopyWinOptimized(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) { - using ffs3::CopyFileCallback; + /* + BackupRead() FileRead() CopyFileEx() + -------------------------------------------- + Attributes NO NO YES + create time NO NO NO + ADS YES NO YES + Encrypted NO(silent fail) NO YES + Compressed NO NO NO + Sparse YES NO NO + PERF 6% faster - + + Mark stream as compressed: FSCTL_SET_COMPRESSION + compatible with: BackupRead() FileRead() + */ - //symbolic link handling - if (copyFileSymLinks && - symlinkExists(sourceFile)) + //open sourceFile for reading + HANDLE hFileIn = ::CreateFile(ffs3::applyLongPathPrefix(sourceFile).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (hFileIn == INVALID_HANDLE_VALUE) { - return copySymlinkInternal(sourceFile, targetFile, SYMLINK_TYPE_FILE, copyFilePermissions); //throw (FileError) + const DWORD lastError = ::GetLastError(); + const wxString& errorMessage = wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + throw ErrorFileLocked(errorMessage); + + throw FileError(errorMessage); } + Loki::ScopeGuard dummy1 = Loki::MakeGuard(::CloseHandle, hFileIn); + (void)dummy1; //silence warning "unused variable" - //begin of regular file copy - struct stat fileInfo; - if (::stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolving symlinks) + + BY_HANDLE_FILE_INFORMATION infoFileIn = {}; + if (!::GetFileInformationByHandle(hFileIn, &infoFileIn)) { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - //open sourceFile for reading - FileInput fileIn(sourceFile); //throw FileError() + const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; + const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + + bool targetSupportSparse = false; + bool targetSupportCompressed = false; + bool targetSupportEncryption = false; + + if (sourceIsCompressed || sourceIsSparse || sourceIsEncrypted) + { + DWORD fsFlags = 0; + + { + const size_t bufferSize = std::max(targetFile.size(), static_cast<size_t>(10000)); + boost::scoped_array<wchar_t> buffer(new wchar_t[bufferSize]); + + //suprisingly slow: ca. 1.5 ms per call! + if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, -> seems to be no need for path prefix (check passed) + buffer.get(), //__out LPTSTR lpszVolumePathName, + static_cast<DWORD>(bufferSize))) //__in DWORD cchBufferLength + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to determine volume name!")); + + Zstring volumePath = buffer.get(); + if (!volumePath.EndsWith(common::FILE_NAME_SEPARATOR)) + volumePath += common::FILE_NAME_SEPARATOR; + + //suprisingly fast: ca. 0.03 ms per call! + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + NULL, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, + NULL, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(volumePath) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to determine volume information!")); + } + + targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; + targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0; + targetSupportEncryption = (fsFlags & FILE_SUPPORTS_ENCRYPTION ) != 0; + } + + //####################################### DST hack ########################################### + if (dst::isFatDrive(sourceFile)) //throw() + { + const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime); + if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + { + infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) + ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available... + } + } + + if (dst::isFatDrive(targetFile)) //throw() + { + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error) + infoFileIn.ftCreationTime = encodedTime.createTimeRaw; + infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw; + } + //####################################### DST hack ########################################### + + const DWORD validAttribs = FILE_ATTRIBUTE_READONLY | + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | // + (targetSupportEncryption ? FILE_ATTRIBUTE_ENCRYPTED : 0); //create targetFile and open it for writing - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set + HANDLE hFileOut = ::CreateFile(ffs3::applyLongPathPrefix(targetFile).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (hFileOut == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + const wxString& errorMessage = wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut + + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFileOut); + (void)dummy; //silence warning "unused variable" + + + const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx + + if (sourceIsCompressed && targetSupportCompressed) + { + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileOut, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL)) //OVERLAPPED structure + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to write NTFS compressed attribute!")); + } + + //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us + if (sourceIsSparse && targetSupportSparse) + { + if (useBackupFun) + { + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileOut, //handle to file + FSCTL_SET_SPARSE, //dwIoControlCode + NULL, //input buffer + 0, //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL)) //OVERLAPPED structure + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to write NTFS sparse attribute!")); + } + } - //ensure cleanup (e.g. network drop): call BEFORE creating fileOut object! - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&ffs3::removeFile, temporary); + const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size + static const boost::scoped_array<BYTE> memory(new BYTE[BUFFER_SIZE]); - FileOutput fileOut(temporary); //throw FileError() + struct ManageCtxt //manage context for BackupRead()/BackupWrite() + { + ManageCtxt() : read(NULL), write(NULL) {} + ~ManageCtxt() + { + if (read != NULL) + ::BackupRead (0, NULL, 0, NULL, true, false, &read); + if (write != NULL) + ::BackupWrite(0, NULL, 0, NULL, true, false, &write); + } - const size_t BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - static const boost::scoped_array<char> memory(new char[BUFFER_SIZE]); + LPVOID read; + LPVOID write; + } context; //copy contents of sourceFile to targetFile wxULongLong totalBytesTransferred; + + bool eof = false; do { - const size_t bytesRead = fileIn.read(memory.get(), BUFFER_SIZE); //throw FileError() + DWORD bytesRead = 0; - fileOut.write(memory.get(), bytesRead); //throw FileError() + if (useBackupFun) + { + if (!::BackupRead(hFileIn, //__in HANDLE hFile, + memory.get(), //__out LPBYTE lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out LPDWORD lpNumberOfBytesRead, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &context.read)) //__out LPVOID *lpContext + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + else if (!::ReadFile(hFileIn, //__in HANDLE hFile, + memory.get(), //__out LPVOID lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + if (bytesRead > BUFFER_SIZE) + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + wxT("buffer overflow")); + + if (bytesRead < BUFFER_SIZE) + eof = true; + + DWORD bytesWritten = 0; + + if (useBackupFun) + { + if (!::BackupWrite(hFileOut, //__in HANDLE hFile, + memory.get(), //__in LPBYTE lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &context.write)) //__out LPVOID *lpContext + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! + } + else if (!::WriteFile(hFileOut, //__in HANDLE hFile, + memory.get(), //__out LPVOID lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! + + if (bytesWritten != bytesRead) + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + wxT("incomplete write")); totalBytesTransferred += bytesRead; @@ -1856,33 +1856,205 @@ void ffs3::copyFile(const Zstring& sourceFile, if (callback != NULL) switch (callback->updateCopyStatus(totalBytesTransferred)) { - case CopyFileCallback::CONTINUE: + case CallbackCopyFile::CONTINUE: break; - case CopyFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error copying file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"\n\n") + _("Operation aborted!")); } } - while (!fileIn.eof()); + while (!eof); - //close output stream before changing attributes - fileOut.close(); - //rename temporary file - ffs3::renameFile(temporary, targetFile); - guardTempFile.Dismiss(); + if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check! + { + LARGE_INTEGER inputSize = {}; + if (!::GetFileSizeEx(hFileIn, &inputSize)) + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + if (inputSize.QuadPart != 0) + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + wxT("unknown error")); + } + + //time needs to be set at the end: BackupWrite() changes file time + if (!::SetFileTime(hFileOut, + &infoFileIn.ftCreationTime, + NULL, + &infoFileIn.ftLastWriteTime)) + throw FileError(wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + +#ifndef NDEBUG //dst hack: verify data written + if (dst::isFatDrive(targetFile)) //throw() + { + WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; + assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName, + GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, + &debugeAttr)); //__out LPVOID lpFileInformation + + assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0); + assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0); + } +#endif + + guardTarget.Dismiss(); + + /* + //create test sparse file + size_t sparseSize = 50 * 1024 * 1024; + HANDLE hSparse = ::CreateFile(L"C:\\sparse.file", + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + DWORD br = 0; + if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &br,NULL)) + throw 1; + + LARGE_INTEGER liDistanceToMove = {}; + liDistanceToMove.QuadPart = sparseSize; + if (!::SetFilePointerEx(hSparse, liDistanceToMove, NULL, FILE_BEGIN)) + throw 1; + + if (!SetEndOfFile(hSparse)) + throw 1; - //ensure cleanup: - Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&ffs3::removeFile, targetFile); + FILE_ZERO_DATA_INFORMATION zeroInfo = {}; + zeroInfo.BeyondFinalZero.QuadPart = sparseSize; + if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), NULL, 0, &br, NULL)) + throw 1; + + ::CloseHandle(hSparse); + */ +} +#endif + + +void rawCopyStream(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) +{ + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: place guard before lifetime of FileOutput + try + { + //open sourceFile for reading + FileInput fileIn(sourceFile); //throw (FileError) + + //create targetFile and open it for writing + FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) + + const size_t BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size + static const boost::scoped_array<char> memory(new char[BUFFER_SIZE]); + + //copy contents of sourceFile to targetFile + wxULongLong totalBytesTransferred; + do + { + const size_t bytesRead = fileIn.read(memory.get(), BUFFER_SIZE); //throw (FileError) + + fileOut.write(memory.get(), bytesRead); //throw (FileError) + + totalBytesTransferred += bytesRead; + + //invoke callback method to update progress indicators + if (callback != NULL) + switch (callback->updateCopyStatus(totalBytesTransferred)) + { + case CallbackCopyFile::CONTINUE: + break; + + case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! + throw FileError(wxString(_("Error copying file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + + zToWx(targetFile) + wxT("\"\n\n") + _("Operation aborted!")); + } + } + while (!fileIn.eof()); + } + catch(ErrorTargetExisting&) + { + guardTarget.Dismiss(); //don't delete file that existed previously! + throw; + } //adapt file modification time: copyFileTimes(sourceFile, targetFile, true); //throw (FileError) + guardTarget.Dismiss(); //target has been created successfully! +} + + +inline +void copyFileImpl(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) +{ +#ifdef FFS_WIN + /* + rawCopyWinApi() rawCopyWinOptimized() + ------------------------------------- + Attributes YES YES + Filetimes YES YES + ADS YES YES + Encrypted YES YES + Compressed NO YES + Sparse NO YES + PERF - 6% faster + */ + + //rawCopyWinApi(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) + rawCopyWinOptimized(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) ->about 8% faster + +#elif defined FFS_LINUX + rawCopyStream(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) +#endif +} +} + + +void ffs3::copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPathMissing, ErrorFileLocked); + const Zstring& targetFile, + bool copyFilePermissions, + CallbackCopyFile* callback) +{ + Zstring temporary = targetFile + ffs3::TEMP_FILE_ENDING; //use temporary file until a correct date has been set + Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&removeFile, boost::cref(temporary)); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!] + + //raw file copy + try + { + copyFileImpl(sourceFile, temporary, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) + } + catch (ErrorTargetExisting&) + { + //determine non-used temp file name "first": + //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses + temporary = createTempName(targetFile); + + //retry + copyFileImpl(sourceFile, temporary, callback); //throw (FileError) + } + + //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite + if (callback) callback->deleteTargetFile(targetFile); + + //rename temporary file: + //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! + renameFile(temporary, targetFile); //throw (FileError) + + guardTempFile.Dismiss(); + Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&removeFile, targetFile); + + //perf: interestingly it is much faster to apply file times BEFORE renaming temporary! + //set file permissions if (copyFilePermissions) - copyObjectPermissions(sourceFile, targetFile, true); //throw FileError() + copyObjectPermissions(sourceFile, targetFile, true); //throw (FileError) guardTargetFile.Dismiss(); //target has been created successfully! } -#endif diff --git a/shared/file_handling.h b/shared/file_handling.h index 4ec9c6d2..1d68c399 100644 --- a/shared/file_handling.h +++ b/shared/file_handling.h @@ -7,26 +7,20 @@ #ifndef FILE_HANDLING_H_INCLUDED #define FILE_HANDLING_H_INCLUDED +#include <wx/longlong.h> #include "zstring.h" #include "file_error.h" -#include <wx/longlong.h> - -#ifdef FFS_WIN -#include "shadow.h" -#endif namespace ffs3 { -struct RemoveDirCallback; -struct MoveFileCallback; -struct CopyFileCallback; - +struct CallbackRemoveDir; +struct CallbackMoveFile; +struct CallbackCopyFile; -Zstring getFormattedDirectoryName(const Zstring& dirname); -bool fileExists( const Zstring& filename); //throw() replaces wxFileExists()! -bool dirExists( const Zstring& dirname); //throw() replaces wxDirExists(): optional 'cause wxDirExists treats symlinks correctly +bool fileExists( const Zstring& filename); //throw() replaces wxFileExists() +bool dirExists( const Zstring& dirname); //throw() replaces wxDirExists() bool symlinkExists( const Zstring& objname); //throw() check whether a symbolic link exists bool somethingExists(const Zstring& objname); //throw() check whether any object with this name exists @@ -43,61 +37,61 @@ ResponseSameVol onSameVolume(const Zstring& folderLeft, const Zstring& folderRig //copy file or directory create/last change date, void copyFileTimes(const Zstring& sourceDir, const Zstring& targetDir, bool derefSymlinks); //throw (FileError) -//copy filesystem permissions: probably requires admin rights -void copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks); //throw FileError(); - //symlink handling: always evaluate target wxULongLong getFilesize(const Zstring& filename); //throw (FileError) //file handling void removeFile(const Zstring& filename); //throw (FileError) -void removeDirectory(const Zstring& directory, RemoveDirCallback* callback = NULL); //throw (FileError) +void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = NULL); //throw (FileError) //rename file or directory: no copying!!! void renameFile(const Zstring& oldName, const Zstring& newName); //throw (FileError); -//move source to target; expectations: target not existing, all super-directories of target exist -void moveFile(const Zstring& sourceFile, const Zstring& targetFile, MoveFileCallback* callback = NULL); //throw (FileError); +//move source to target; expectations: all super-directories of target exist +//"ignoreExisting": if target already exists, source is deleted +void moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback); //throw (FileError); //move source to target including subdirectories -//"ignoreExistingDirs": existing directories will be enhanced as long as this is possible without overwriting files -void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback = NULL); //throw (FileError); +//"ignoreExisting": existing directories and files will be enriched +void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback); //throw (FileError); //creates superdirectories automatically: -void createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions); //throw (FileError); +void createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw (FileError); void createDirectory(const Zstring& directory); //throw (FileError); -> function overload avoids default parameter ambiguity issues! -void copyFile(const Zstring& sourceFile, //throw (FileError); +void copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPathMissing, ErrorFileLocked (Windows-only)); const Zstring& targetFile, - bool copyFileSymLinks, bool copyFilePermissions, -#ifdef FFS_WIN - shadow::ShadowCopy* shadowCopyHandler, //supply handler for making shadow copies, may be NULL -#endif - CopyFileCallback* callback); //may be NULL + CallbackCopyFile* callback); //may be NULL //Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop. // => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending: const Zstring TEMP_FILE_ENDING = Zstr(".ffs_tmp"); +enum SymlinkType +{ + SYMLINK_TYPE_FILE, + SYMLINK_TYPE_DIR +}; +void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, SymlinkType type, bool copyFilePermissions); //throw (FileError) //----------- callbacks --------------- -struct RemoveDirCallback +struct CallbackRemoveDir { - virtual ~RemoveDirCallback() {} + virtual ~CallbackRemoveDir() {} virtual void requestUiRefresh(const Zstring& currentObject) = 0; }; -struct MoveFileCallback //callback functionality +struct CallbackMoveFile //callback functionality { - virtual ~MoveFileCallback() {} + virtual ~CallbackMoveFile() {} enum Response { @@ -108,9 +102,13 @@ struct MoveFileCallback //callback functionality }; -struct CopyFileCallback //callback functionality +struct CallbackCopyFile //callback functionality { - virtual ~CopyFileCallback() {} + virtual ~CallbackCopyFile() {} + + //if target is existing user needs to implement deletion: copyFile() NEVER deletes target if already existing! + //at this point full read access on source had been proven, so it's safe to delete it. + virtual void deleteTargetFile(const Zstring& targetFile) = 0; //may throw exceptions enum Response { diff --git a/shared/file_io.cpp b/shared/file_io.cpp index e6de77bb..0afe17dd 100644 --- a/shared/file_io.cpp +++ b/shared/file_io.cpp @@ -5,9 +5,9 @@ // ************************************************************************** // #include "file_io.h" -#include <wx/intl.h> #include "string_conv.h" #include "system_func.h" +#include "i18n.h" #ifdef FFS_WIN #include "long_path_prefix.h" @@ -18,7 +18,15 @@ using namespace ffs3; -FileInput::FileInput(const Zstring& filename) : //throw FileError() +FileInput::FileInput(FileHandle handle, const Zstring& filename) : +#ifdef FFS_WIN + eofReached(false), +#endif + fileHandle(handle), + filename_(filename) {} + + +FileInput::FileInput(const Zstring& filename) : //throw (FileError, ErrorNotExisting) #ifdef FFS_WIN eofReached(false), #endif @@ -63,19 +71,19 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError() if (lastError == ERROR_FILE_NOT_FOUND || lastError == ERROR_PATH_NOT_FOUND) throw ErrorNotExisting(errorMessage); - else - throw FileError(errorMessage); + + throw FileError(errorMessage); } #elif defined FFS_LINUX - fileHandle = ::fopen(filename.c_str(), "rb,type=record,noseek"); //utilize UTF-8 filename + fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename if (fileHandle == NULL) { const int lastError = errno; const wxString& errorMessage = wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); if (lastError == ENOENT) throw ErrorNotExisting(errorMessage); - else - throw FileError(errorMessage); + + throw FileError(errorMessage); } #endif } @@ -91,25 +99,28 @@ FileInput::~FileInput() } -size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw FileError() +size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw (FileError) { #ifdef FFS_WIN DWORD bytesRead = 0; - if (!::ReadFile( - fileHandle, //__in HANDLE hFile, - buffer, //__out LPVOID lpBuffer, - static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead, - &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, - NULL) //__inout_opt LPOVERLAPPED lpOverlapped - || bytesRead > bytesToRead) //must be fulfilled + if (!::ReadFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted()); + if (bytesRead > bytesToRead) + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + + wxT("\n\n") + wxT("buffer overflow")); + if (bytesRead < bytesToRead) - eofReached = true; + eofReached = bytesRead < bytesToRead; return bytesRead; + #elif defined FFS_LINUX const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); if (::ferror(fileHandle) != 0) @@ -130,49 +141,83 @@ bool FileInput::eof() //end of file reached } -FileOutput::FileOutput(const Zstring& filename) : //throw FileError() +FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {} + + +FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw (FileError, ErrorTargetPathMissing, ErrorTargetExisting) filename_(filename) { #ifdef FFS_WIN fileHandle = ::CreateFile(ffs3::applyLongPathPrefix(filename).c_str(), GENERIC_WRITE, - FILE_SHARE_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //note: FILE_SHARE_DELETE is required to rename file while handle is open! NULL, - CREATE_ALWAYS, + access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); if (fileHandle == INVALID_HANDLE_VALUE) - throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted()); + { + const DWORD lastError = ::GetLastError(); + const wxString& errorMessage = wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } + #elif defined FFS_LINUX - fileHandle = ::fopen(filename.c_str(), "wb,type=record,noseek"); //utilize UTF-8 filename - if (!fileHandle) - throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted()); + fileHandle = ::fopen(filename.c_str(), + //GNU extension: https://www.securecoding.cert.org/confluence/display/cplusplus/FIO03-CPP.+Do+not+make+assumptions+about+fopen()+and+file+creation + access == ACC_OVERWRITE ? "w,type=record,noseek" : "wx,type=record,noseek"); + if (fileHandle == NULL) + { + const int lastError = errno; + const wxString& errorMessage = wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ENOENT) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } #endif } FileOutput::~FileOutput() { - close(); //may be called more than once +#ifdef FFS_WIN + ::CloseHandle(fileHandle); +#elif defined FFS_LINUX + ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash! +#endif } -void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError() +void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw (FileError) { #ifdef FFS_WIN DWORD bytesWritten = 0; - if (!::WriteFile( - fileHandle, //__in HANDLE hFile, - buffer, //__out LPVOID lpBuffer, - static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToRead, - &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, - NULL) //__inout_opt LPOVERLAPPED lpOverlapped - || bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! + if (!::WriteFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! + + if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(filename_) + wxT("\"") + + wxT("\n\n") + wxT("incomplete write")); + #elif defined FFS_LINUX const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); if (::ferror(fileHandle) != 0 || bytesWritten != bytesToWrite) @@ -180,17 +225,3 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! #endif } - - -void FileOutput::close() //close file stream -{ - if (fileHandle != NULL) //NEVER allow passing NULL to fclose! -> crash! - { -#ifdef FFS_WIN - ::CloseHandle(fileHandle); -#elif defined FFS_LINUX - ::fclose(fileHandle); -#endif - fileHandle = NULL; - } -} diff --git a/shared/file_io.h b/shared/file_io.h index 0781595b..a1460f53 100644 --- a/shared/file_io.h +++ b/shared/file_io.h @@ -22,22 +22,27 @@ namespace ffs3 { //file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) +#ifdef FFS_WIN +typedef HANDLE FileHandle; +#elif defined FFS_LINUX +typedef FILE* FileHandle; +#endif + class FileInput { public: - FileInput(const Zstring& filename); //throw (FileError, ErrorNotExisting) + FileInput(const Zstring& filename); //throw (FileError: ErrorNotExisting) + FileInput(FileHandle handle, const Zstring& filename); //takes ownership! ~FileInput(); - size_t read(void* buffer, size_t bytesToRead); //throw FileError(); returns actual number of bytes read + size_t read(void* buffer, size_t bytesToRead); //throw (FileError); returns actual number of bytes read bool eof(); //end of file reached private: #ifdef FFS_WIN - HANDLE fileHandle; bool eofReached; -#elif defined FFS_LINUX - FILE* fileHandle; #endif + FileHandle fileHandle; const Zstring filename_; }; @@ -45,17 +50,19 @@ private: class FileOutput { public: - FileOutput(const Zstring& filename); //throw FileError() + enum AccessFlag + { + ACC_OVERWRITE, + ACC_CREATE_NEW + }; + FileOutput(const Zstring& filename, AccessFlag access); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) + FileOutput(FileHandle handle, const Zstring& filename); //takes ownership! ~FileOutput(); - void write(const void* buffer, size_t bytesToWrite); //throw FileError() - void close(); //close file stream + void write(const void* buffer, size_t bytesToWrite); //throw (FileError) + private: -#ifdef FFS_WIN - HANDLE fileHandle; -#elif defined FFS_LINUX - FILE* fileHandle; -#endif + FileHandle fileHandle; const Zstring filename_; }; @@ -65,11 +72,11 @@ private: class FileInputStream : public wxInputStream { public: - FileInputStream(const Zstring& filename) : //throw FileError() + FileInputStream(const Zstring& filename) : //throw (FileError) fileObj(filename) {} private: - virtual size_t OnSysRead(void* buffer, size_t bufsize) //throw FileError() + virtual size_t OnSysRead(void* buffer, size_t bufsize) //throw (FileError) { return fileObj.read(buffer, bufsize); } @@ -81,11 +88,11 @@ private: class FileOutputStream : public wxOutputStream { public: - FileOutputStream(const Zstring& filename) : //throw FileError() - fileObj(filename) {} + FileOutputStream(const Zstring& filename) : //throw (FileError) + fileObj(filename, FileOutput::ACC_OVERWRITE) {} private: - virtual size_t OnSysWrite(const void* buffer, size_t bufsize) //throw FileError() + virtual size_t OnSysWrite(const void* buffer, size_t bufsize) //throw (FileError) { fileObj.write(buffer, bufsize); return bufsize; diff --git a/shared/file_traverser.cpp b/shared/file_traverser.cpp index f0777898..2af0f4af 100644 --- a/shared/file_traverser.cpp +++ b/shared/file_traverser.cpp @@ -8,7 +8,6 @@ #include <limits> #include "system_constants.h" #include "system_func.h" -#include <wx/intl.h> #include "string_conv.h" #include "assert_static.h" #include "symlink_target.h" diff --git a/shared/localization.cpp b/shared/i18n.cpp index a412df1e..537d9fb4 100644 --- a/shared/localization.cpp +++ b/shared/i18n.cpp @@ -4,32 +4,32 @@ // * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * // ************************************************************************** // -#include "localization.h" +#include "i18n.h" +#include <fstream> +#include <map> +#include <wx/ffile.h> +#include <wx/intl.h> #include <wx/msgdlg.h> #include "../shared/standard_paths.h" #include "../shared/string_conv.h" #include "system_constants.h" -#include <fstream> -#include <map> -#include <wx/ffile.h> +#include <boost/any.hpp> +#include <boost/shared_ptr.hpp> +#include <list> -#if wxCHECK_VERSION(2, 9, 1) -#include <boost/cstdint.hpp> -#include <wx/translation.h> -#include <cstdlib> -#endif - -using ffs3::CustomLocale; using ffs3::LocalizationInfo; -//_("Browse") <- dummy string for wxDirPickerCtrl to be recognized by automatic text extraction! - namespace { //will receive their proper value in CustomLocale::CustomLocale() wxString THOUSANDS_SEPARATOR = wxT(","); wxString DECIMAL_POINT = wxT("."); + +typedef std::map<wxString, wxString> Translation; +Translation activeTranslation; //map original text |-> translation + +int activeLanguage = wxLANGUAGE_ENGLISH; } @@ -45,25 +45,13 @@ wxString ffs3::getDecimalPoint() } -const std::vector<ffs3::LocInfoLine>& LocalizationInfo::getMapping() +const std::vector<ffs3::LocInfoLine>& LocalizationInfo::get() { static LocalizationInfo instance; return instance.locMapping; } -namespace -{ -struct CompareByName : public std::binary_function<ffs3::LocInfoLine, ffs3::LocInfoLine, bool> -{ - bool operator()(const ffs3::LocInfoLine& lhs, const ffs3::LocInfoLine& rhs) const - { - return lhs.languageName < rhs.languageName; - } -}; -} - - LocalizationInfo::LocalizationInfo() { ffs3::LocInfoLine newEntry; @@ -242,8 +230,6 @@ LocalizationInfo::LocalizationInfo() newEntry.translatorName = wxT("Simon Park"); newEntry.languageFlag = wxT("south_korea.png"); locMapping.push_back(newEntry); - - //std::sort(locMapping.begin(), locMapping.end(), CompareByName()); } @@ -330,7 +316,7 @@ int mapLanguageDialect(int language) //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: //case wxLANGUAGE_KOREAN: - //variants of wxLANGUAGE_ARABIC (also needed to detect RTL languages) + //variants of wxLANGUAGE_ARABIC case wxLANGUAGE_ARABIC_ALGERIA: case wxLANGUAGE_ARABIC_BAHRAIN: case wxLANGUAGE_ARABIC_EGYPT: @@ -472,18 +458,13 @@ private: }; -typedef std::map<wxString, wxString> TranslationMap; //map original text |-> translation - -void loadTranslation(const wxString& filename, TranslationMap& trans) //empty translation on error +void loadTranslation(const wxString& filename, Translation& trans) //empty translation on error { trans.clear(); UnicodeFileReader langFile(ffs3::getResourceDir() + wxT("Languages") + ffs3::zToWx(common::FILE_NAME_SEPARATOR) + filename); if (langFile.isOkay()) { - //save encoding info: required by mo file generator - trans.insert(std::make_pair(wxEmptyString, wxT("Content-Type: text/plain; charset=UTF-8\n"))); - int rowNumber = 0; wxString original; wxString tmpString; @@ -506,195 +487,265 @@ void loadTranslation(const wxString& filename, TranslationMap& trans) //empty tr } -#if wxCHECK_VERSION(2, 9, 1) -//this whole abomination is required to support language formats other than "mo" in wxWidgets v2.9 -class FFSTranslationLoader : public wxTranslationsLoader +void ffs3::setLanguage(int language) { -public: - static const wxString domainName() + static class StaticInit { - return wxT("FFS"); - } + public: + StaticInit() : loc(wxLANGUAGE_DEFAULT) //wxLocale: we need deferred initialization, sigh... + { + //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale + const lconv* localInfo = ::localeconv(); - FFSTranslationLoader(const TranslationMap& trans, wxLanguage langId) : langId_(langId) - { - //generate mo file: http://www.gnu.org/software/hello/manual/gettext/MO-Files.html + //actually these two parameters are language dependent, but we take system setting to handle all kinds of language derivations + THOUSANDS_SEPARATOR = wxString::FromUTF8(localInfo->thousands_sep); + DECIMAL_POINT = wxString::FromUTF8(localInfo->decimal_point); - std::string binaryStream; + // why not working? + // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).thousands_sep(); + // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).decimal_point(); + } + private: + wxLocale loc; //required for RTL language support (and nothing else) + } dummy; - const size_t offsetTableOrig = sizeof(wxMsgCatalogHeader); - const size_t offsetTableTrans = offsetTableOrig + trans.size() * sizeof(wxMsgTableEntry); - const size_t offsetTableString = offsetTableTrans + trans.size() * sizeof(wxMsgTableEntry); + activeLanguage = language; - wxMsgCatalogHeader header = - { - 0x950412de, //magic number (save in this machine's byte order) - 0, //revision - trans.size(), //numStrings - offsetTableOrig, //ofsOrigTable - offsetTableTrans, //ofsTransTable - 0, //nHashSize - 0, //ofsHashTable - }; - writeCobject(binaryStream, header); - - std::string tableOrig; - std::string tableTrans; - std::string stringsList; - for (TranslationMap::const_iterator i = trans.begin(); i != trans.end(); ++i) - { + //default: english + wxString languageFile; -#ifndef _MSC_VER -#warning redundant UTF8 conversion!!! -#endif - std::string origString = i->first.ToUTF8(); - const wxMsgTableEntry origEntry = {origString.length(), offsetTableString + stringsList.size()}; - writeCobject(tableOrig, origEntry); - stringsList.append(origString.c_str(), origString.length() + 1); //include NULL-termination - -#ifndef _MSC_VER -#warning redundant UTF8 conversion!!! -#endif - std::string transString = i->second.ToUTF8(); - const wxMsgTableEntry transEntry = {transString.length(), offsetTableString + stringsList.size()}; - writeCobject(tableTrans, transEntry); - stringsList.append(transString.c_str(), transString.length() + 1); //include NULL-termination + //(try to) retrieve language filename + const int mappedLanguage = mapLanguageDialect(language); + for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::get().begin(); i != LocalizationInfo::get().end(); ++i) + if (i->languageID == mappedLanguage) + { + languageFile = i->languageFile; + break; } - binaryStream += tableOrig; - binaryStream += tableTrans; - binaryStream += stringsList; - buffer = wxScopedCharBuffer::CreateOwned(static_cast<char*>(::malloc(binaryStream.size())), binaryStream.size()); //takes buffer ownership, calls ::free() - std::copy(binaryStream.begin(), binaryStream.end(), buffer.data()); + //load language file into buffer + activeTranslation.clear(); + if (!languageFile.empty()) + { + loadTranslation(languageFile, activeTranslation); //empty translation on error + if (activeTranslation.empty()) + { + wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + languageFile + wxT("\""), _("Error"), wxOK | wxICON_ERROR); + activeLanguage = wxLANGUAGE_ENGLISH; //reset to english language to show this error just once + } } + else + ; //if languageFile is empty texts will be english per default +} - virtual wxMsgCatalog* LoadCatalog(const wxString& domain, const wxString& lang) - { - if (domain != domainName() || lang != wxLocale::GetLanguageCanonicalName(langId_)) //avoid superfluous calls by wxWidgets framework - return NULL; - return wxMsgCatalog::CreateFromData(buffer, domain); - } +int ffs3::getLanguage() +{ + return activeLanguage; +} - virtual wxArrayString GetAvailableTranslations(const wxString& domain) const - { - wxArrayString output; - if (domain == domainName()) - output.Add(wxLocale::GetLanguageCanonicalName(langId_)); - return output; - } -private: - struct wxMsgTableEntry - { - boost::uint32_t nLen, // length of the string - ofsString; // pointer to the string - }; +int ffs3::retrieveSystemLanguage() +{ + return wxLocale::GetSystemLanguage(); +} - // header of a .mo file - struct wxMsgCatalogHeader - { - boost::uint32_t magic, // offset +00: magic id - revision, // +04: revision - numStrings, // +08: number of strings in the file - ofsOrigTable, // +0C: start of original string table - ofsTransTable, // +10: start of translated string table - nHashSize, // +14: hash table size - ofsHashTable; // +18: offset of hash table start - }; - template <class T> - void writeCobject(std::string& str, T obj) - { - str.append(reinterpret_cast<const char*>(&obj), sizeof(obj)); - } - wxScopedCharBuffer buffer; //raw data in mo file format - const wxLanguage langId_; + +/* +typedef Zbase<wchar_t> Wstring; + +const Wstring TK_TERNARY_QUEST = L"?"; +const Wstring TK_TERNARY_COLON = L":"; +const Wstring TK_OR = L"||"; +const Wstring TK_AND = L"&&"; +const Wstring TK_EQUAL = L"=="; +const Wstring TK_NOT_EQUAL = L"!="; +const Wstring TK_LESS = L"<"; +const Wstring TK_LESS_EQUAL = L"<="; +const Wstring TK_GREATER = L">"; +const Wstring TK_GREATER_EQUAL = L">="; +const Wstring TK_MODULUS = L"%"; +const Wstring TK_N = L"n"; +const Wstring TK_BRACKET_LEFT = L"("; +const Wstring TK_BRACKET_RIGHT = L")"; +const Wstring TK_FORM_COUNT = L"nplurals="; +const Wstring TK_PHRASE_BEGIN = L"plural="; + + +template <class T> +struct Expr +{ + typedef T ValueType; + virtual ValueType eval() const = 0; }; -#endif -CustomLocale& CustomLocale::getInstance() +template <class In, class Out, class BiOp> +struct GenericBiExp : public Out { - static CustomLocale instance; - return instance; + GenericBiExp(const In& lhs, const In& rhs, BiOp biop) : lhs_(lhs), rhs_(rhs), biop_(biop) {} + virtual typename Out::ValueType eval() const { return biop_(lhs_.eval(), rhs_.eval()); } + const In& lhs_; + const In& rhs_; + BiOp biop_; +}; + +template <class Out, class In, class BiOp> +inline +GenericBiExp<In, Out, BiOp> makeBiExp(const In& lhs, const In& rhs, BiOp biop) +{ + return GenericBiExp<In, Out, BiOp>(lhs, rhs, biop); } +template <class Out> +struct TernaryExp : public Out +{ + TernaryExp(const Expr<bool>& ifExp, const Out& thenExp, const Out& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) {} + virtual typename Out::ValueType eval() const { return ifExp_.eval() ? thenExp_.eval() : elseExp_.eval(); } + const Expr<bool>& ifExp_; + const Out& thenExp_; + const Out& elseExp_; +}; + +struct LiteralNumberEx : public Expr<int> +{ + LiteralNumberEx(int n) : n_(n) {} + virtual ValueType eval() const { return n_; } + int n_; +}; -class Translation : public TranslationMap {}; +struct NumberN : public Expr<int> +{ + NumberN(int& n) : n_(n) {} + virtual ValueType eval() const { return n_; } + int& n_; +}; -CustomLocale::CustomLocale() : - translationDB(new Translation), - currentLanguage(wxLANGUAGE_ENGLISH) +class PluralForm { - Init(wxLANGUAGE_DEFAULT); //setting a different language needn't be supported on all systems! +public: + struct ParserError {}; - //actually these two parameters are language dependent, but we take system setting to handle all kinds of language derivations - const lconv* localInfo = localeconv(); - THOUSANDS_SEPARATOR = wxString::FromUTF8(localInfo->thousands_sep); - DECIMAL_POINT = wxString::FromUTF8(localInfo->decimal_point); + PluralForm(const Wstring& phrase) //.po format,e.g.: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) + { + Wstring tmp; + std::remove_copy_if(phrase.begin(), phrase.end(), std::back_inserter(tmp), std::iswspace); - // why not working? - // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).thousands_sep(); - // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).decimal_point(); -} + size_t pos = tmp.find(TK_FORM_COUNT); + if (pos == Wstring::npos) + throw ParserError(); + count = tmp.substr(pos + TK_FORM_COUNT.size()).toNumber<int>(); -CustomLocale::~CustomLocale() {} //non-inline destructor for std::auto_ptr to work with forward declaration + pos = tmp.find(TK_PHRASE_BEGIN); + if (pos == Wstring::npos) + throw ParserError(); + expr = &parseNumber(tmp.substr(pos + TK_PHRASE_BEGIN.size())); + } -void CustomLocale::setLanguage(int language) -{ - currentLanguage = static_cast<wxLanguage>(language); + int formCount() const { return count; } - //default: english - wxString languageFile; + int getForm(int n) const { n_ = n ; return expr->eval(); } - //(try to) retrieve language filename - const int mappedLanguage = mapLanguageDialect(language); - for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::getMapping().begin(); i != LocalizationInfo::getMapping().end(); ++i) - if (i->languageID == mappedLanguage) - { - languageFile = i->languageFile; - break; - } +private: + const Expr<bool>& parseBool(const Wstring& phrase) + { + Wstring part1, part2; + if (trySplit(phrase, TK_OR, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseBool(part1), parseBool(part2), std::logical_or<bool>())); + else if (trySplit(phrase, TK_AND, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseBool(part1), parseBool(part2), std::logical_and<bool>())); + else if (trySplit(phrase, TK_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::equal_to<int>())); + else if (trySplit(phrase, TK_NOT_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::not_equal_to<int>())); + else if (trySplit(phrase, TK_LESS, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::less<int>())); + else if (trySplit(phrase, TK_LESS_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::less_equal<int>())); + else if (trySplit(phrase, TK_GREATER, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::greater<int>())); + else if (trySplit(phrase, TK_GREATER_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::greater_equal<int>())); + else + throw ParserError(); + } - //load language file into buffer - translationDB->clear(); - if (!languageFile.empty()) + const Expr<int>& parseNumber(const Wstring& phrase) { - loadTranslation(languageFile, *translationDB); //empty translation on error - if (translationDB->empty()) + Wstring part1, part2; + + + if (trySplit(phrase, TK_TERNARY_QUEST, part1, part2, TO_LEFT)) { - wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + languageFile + wxT("\""), _("Error"), wxOK | wxICON_ERROR); - currentLanguage = wxLANGUAGE_ENGLISH; //reset to english language to show this error just once + Wstring part3; + if (!trySplit(Wstring(part2), TK_TERNARY_COLON, part2, part3, TO_LEFT)) + throw ParserError(); + return manageObj(TernaryExp<Expr<int> >(parseBool(part1), parseNumber(part2), parseNumber(part3))); + } + else if (trySplit(phrase, TK_MODULUS, part1, part2)) + return manageObj(makeBiExp<Expr<int> >(parseNumber(part1), parseNumber(part2), std::modulus<int>())); + else if (phrase == TK_N) + return manageObj(NumberN(n_)); + else //expect literal number + { + if (std::find_if(phrase.begin(), phrase.end(), std::not1(std::ptr_fun(std::iswdigit))) != phrase.end()) + throw ParserError(); + return manageObj(LiteralNumberEx(phrase.toNumber<int>())); } } - else - ; //if languageFile is empty texts will be english per default + enum Associativity + { + TO_LEFT, + TO_RIGHT + }; + static bool trySplit(const Wstring& phrase, const Wstring& delimiter, Wstring& part1, Wstring& part2, Associativity assoc = TO_RIGHT) + { + size_t pos = assoc == TO_LEFT ? + phrase.find(delimiter) : //reverse [!] + phrase.rfind(delimiter); + if (pos == Wstring::npos) + return false; + + part1 = phrase.substr(0, pos); + part2 = phrase.substr(pos + delimiter.size()); + return true; + } + + template <class T> + const T& manageObj(const T& obj) + { + dump.push_back(obj); + return *boost::any_cast<T>(&dump.back()); + } + int count; + const Expr<int>* expr; + mutable int n_; + + std::list<boost::any> dump; //manage polymorphc object lifetimes +}; -#if wxCHECK_VERSION(2, 9, 1) - wxTranslations::Set(new wxTranslations); - wxTranslations::Get()->SetLoader(new FFSTranslationLoader(*translationDB, currentLanguage)); //ownership passed - wxTranslations::Get()->SetLanguage(currentLanguage); - wxTranslations::Get()->AddCatalog(FFSTranslationLoader::domainName(), wxLANGUAGE_ENGLISH_US); - //... a little over design going on?!? -#endif -} + PluralForm dummy(L"nplurals=3; plural=n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 > 20) ? 1 : 2"); + int ddf = dummy.formCount(); + int kddf = dummy.getForm(0); +*/ -const wxChar* CustomLocale::GetString(const wxChar* szOrigString, const wxChar* szDomain) const + + +wxString ffs3::translate(const wxString& original) //translate into currently selected language { //look for translation in buffer table - const Translation::const_iterator i = translationDB->find(szOrigString); - if (i != translationDB->end()) + const Translation::const_iterator i = activeTranslation.find(original); + if (i != activeTranslation.end()) return i->second.c_str(); //fallback - return szOrigString; + return original; } - diff --git a/shared/localization.h b/shared/i18n.h index 3ce7bf24..0026cdd5 100644 --- a/shared/localization.h +++ b/shared/i18n.h @@ -7,23 +7,14 @@ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED -#include <wx/intl.h> +#include <wx/string.h> #include <vector> -#include <memory> - -class Translation; - namespace ffs3 { -//language independent global variables: just use operating system's default setting! -wxString getThousandsSeparator(); -wxString getDecimalPoint(); - - struct LocInfoLine { - wxLanguage languageID; + int languageID; wxString languageName; wxString languageFile; wxString translatorName; @@ -33,7 +24,7 @@ struct LocInfoLine class LocalizationInfo { public: - static const std::vector<LocInfoLine>& getMapping(); + static const std::vector<LocInfoLine>& get(); private: LocalizationInfo(); @@ -44,27 +35,24 @@ private: }; -class CustomLocale : public wxLocale -{ -public: - static CustomLocale& getInstance(); - - void setLanguage(int language); +//language independent global variables: just use operating system's default setting! +wxString getThousandsSeparator(); +wxString getDecimalPoint(); - int getLanguage() const - { - return currentLanguage; - } +void setLanguage(int language); +int getLanguage(); - virtual const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; +int retrieveSystemLanguage(); -private: - CustomLocale(); - ~CustomLocale(); //non-inline destructor for std::auto_ptr to work with forward declaration -> superfluous in this case: singleton pattern! +wxString translate(const wxString& original); //translate into currently selected language - std::auto_ptr<Translation> translationDB; //careful with forward-declarations and auto_ptr! save in this case, 'cause full class info available - wxLanguage currentLanguage; -}; +//translate plural forms: "%x day|%x days" +//returns "%x day" if n == 1; "%x days" else for english language +wxString translate(const wxString& original, int n); } +//WXINTL_NO_GETTEXT_MACRO must be defined to deactivate wxWidgets underscore macro +#define _(s) ffs3::translate(wxT(s)) +#define _P(s, n) ffs3::translate(wxT(s), n) + #endif // MISC_H_INCLUDED diff --git a/shared/i18n_no_BOM.cpp b/shared/i18n_no_BOM.cpp new file mode 100644 index 00000000..78f2b230 --- /dev/null +++ b/shared/i18n_no_BOM.cpp @@ -0,0 +1,751 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// +#include "i18n.h" +#include <fstream> +#include <map> +#include <wx/ffile.h> +#include <wx/intl.h> +#include <wx/msgdlg.h> +#include "../shared/standard_paths.h" +#include "../shared/string_conv.h" +#include "system_constants.h" +#include <boost/any.hpp> +#include <boost/shared_ptr.hpp> +#include <list> + +using ffs3::LocalizationInfo; + + +namespace +{ +//will receive their proper value in CustomLocale::CustomLocale() +wxString THOUSANDS_SEPARATOR = wxT(","); +wxString DECIMAL_POINT = wxT("."); + +typedef std::map<wxString, wxString> Translation; +Translation activeTranslation; //map original text |-> translation + +int activeLanguage = wxLANGUAGE_ENGLISH; +} + + +wxString ffs3::getThousandsSeparator() +{ + return THOUSANDS_SEPARATOR; +} + + +wxString ffs3::getDecimalPoint() +{ + return DECIMAL_POINT; +} + + +const std::vector<ffs3::LocInfoLine>& LocalizationInfo::get() +{ + static LocalizationInfo instance; + return instance.locMapping; +} + + +LocalizationInfo::LocalizationInfo() +{ + ffs3::LocInfoLine newEntry; + + newEntry.languageID = wxLANGUAGE_CZECH; + newEntry.languageName = wxT("Čeština"); + newEntry.languageFile = wxT("czech.lng"); + newEntry.translatorName = wxT("ViCi"); + newEntry.languageFlag = wxT("czechRep.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_GERMAN; + newEntry.languageName = wxT("Deutsch"); + newEntry.languageFile = wxT("german.lng"); + newEntry.translatorName = wxT("ZenJu"); + newEntry.languageFlag = wxT("germany.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ENGLISH_UK; + newEntry.languageName = wxT("English (UK)"); + newEntry.languageFile = wxT("english_uk.lng"); + newEntry.translatorName = wxT("Robert Readman"); + newEntry.languageFlag = wxT("england.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ENGLISH; + newEntry.languageName = wxT("English (US)"); + newEntry.languageFile = wxT(""); + newEntry.translatorName = wxT("ZenJu"); + newEntry.languageFlag = wxT("usa.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_SPANISH; + newEntry.languageName = wxT("Español"); + newEntry.languageFile = wxT("spanish.lng"); + newEntry.translatorName = wxT("Alexis Martínez"); + newEntry.languageFlag = wxT("spain.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_GREEK; + newEntry.languageName = wxT("Ελληνικά"); + newEntry.languageFile = wxT("greek.lng"); + newEntry.translatorName = wxT("Γιώργος Γιαγλής"); + newEntry.languageFlag = wxT("greece.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_FRENCH; + newEntry.languageName = wxT("Français"); + newEntry.languageFile = wxT("french.lng"); + newEntry.translatorName = wxT("Jean-François Hartmann"); + newEntry.languageFlag = wxT("france.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ITALIAN; + newEntry.languageName = wxT("Italiano"); + newEntry.languageFile = wxT("italian.lng"); + newEntry.translatorName = wxT("Emmo"); + newEntry.languageFlag = wxT("italy.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_HUNGARIAN; + newEntry.languageName = wxT("Magyar"); + newEntry.languageFile = wxT("hungarian.lng"); + newEntry.translatorName = wxT("Demon"); + newEntry.languageFlag = wxT("hungary.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_DUTCH; + newEntry.languageName = wxT("Nederlands"); + newEntry.languageFile = wxT("dutch.lng"); + newEntry.translatorName = wxT("Dion van Lieshout"); + newEntry.languageFlag = wxT("holland.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_POLISH; + newEntry.languageName = wxT("Polski"); + newEntry.languageFile = wxT("polish.lng"); + newEntry.translatorName = wxT("Wojtek Pietruszewski"); + newEntry.languageFlag = wxT("poland.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_PORTUGUESE; + newEntry.languageName = wxT("Português"); + newEntry.languageFile = wxT("portuguese.lng"); + newEntry.translatorName = wxT("QuestMark"); + newEntry.languageFlag = wxT("portugal.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_PORTUGUESE_BRAZILIAN; + newEntry.languageName = wxT("Português do Brasil"); + newEntry.languageFile = wxT("portuguese_br.lng"); + newEntry.translatorName = wxT("Edison Aranha"); + newEntry.languageFlag = wxT("brazil.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_RUSSIAN; + newEntry.languageName = wxT("Pусский"); + newEntry.languageFile = wxT("russian.lng"); + newEntry.translatorName = wxT("Fayzullin T.N. aka Svobodniy"); + newEntry.languageFlag = wxT("russia.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_ROMANIAN; + newEntry.languageName = wxT("Română"); + newEntry.languageFile = wxT("romanian.lng"); + newEntry.translatorName = wxT("Alexandru Bogdan Munteanu"); + newEntry.languageFlag = wxT("romania.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_SLOVENIAN; + newEntry.languageName = wxT("Slovenščina"); + newEntry.languageFile = wxT("slovenian.lng"); + newEntry.translatorName = wxT("Matej Badalic"); + newEntry.languageFlag = wxT("slovakia.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_FINNISH; + newEntry.languageName = wxT("Suomi"); + newEntry.languageFile = wxT("finnish.lng"); + newEntry.translatorName = wxT("Nalle Juslén"); + newEntry.languageFlag = wxT("finland.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_SWEDISH; + newEntry.languageName = wxT("Svenska"); + newEntry.languageFile = wxT("swedish.lng"); + newEntry.translatorName = wxT("Åke Engelbrektson"); + newEntry.languageFlag = wxT("sweden.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_TURKISH; + newEntry.languageName = wxT("Türkçe"); + newEntry.languageFile = wxT("turkish.lng"); + newEntry.translatorName = wxT("Kaya Zeren"); + newEntry.languageFlag = wxT("turkey.png"); + locMapping.push_back(newEntry); + + // newEntry.languageID = wxLANGUAGE_HEBREW; + // newEntry.languageName = wxT("עִבְרִית"); + // newEntry.languageFile = wxT("hebrew.lng"); + // newEntry.translatorName = wxT("Moshe Olshevsky"); + // newEntry.languageFlag = wxT("isreal.png"); + // locMapping.push_back(newEntry); + + // newEntry.languageID = wxLANGUAGE_ARABIC; + // newEntry.languageName = wxT("العربية"); + // newEntry.languageFile = wxT("arabic.lng"); + // newEntry.translatorName = wxT("Yousef Shamshoum"); + // newEntry.languageFlag = wxT("arabic-language.png"); + // locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_JAPANESE; + newEntry.languageName = wxT("日本語"); + newEntry.languageFile = wxT("japanese.lng"); + newEntry.translatorName = wxT("Tilt"); + newEntry.languageFlag = wxT("japan.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_CHINESE_TRADITIONAL; + newEntry.languageName = wxT("正體中文"); + newEntry.languageFile = wxT("chinese_traditional.lng"); + newEntry.translatorName = wxT("Carlos"); + newEntry.languageFlag = wxT("taiwan.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_CHINESE_SIMPLIFIED; + newEntry.languageName = wxT("简体中文"); + newEntry.languageFile = wxT("chinese_simple.lng"); + newEntry.translatorName = wxT("CyberCowBoy"); + newEntry.languageFlag = wxT("china.png"); + locMapping.push_back(newEntry); + + newEntry.languageID = wxLANGUAGE_KOREAN; + newEntry.languageName = wxT("한국어"); + newEntry.languageFile = wxT("korean.lng"); + newEntry.translatorName = wxT("Simon Park"); + newEntry.languageFlag = wxT("south_korea.png"); + locMapping.push_back(newEntry); +} + + +namespace +{ +int mapLanguageDialect(int language) +{ + switch (language) //map language dialects + { + //variants of wxLANGUAGE_GERMAN + case wxLANGUAGE_GERMAN_AUSTRIAN: + case wxLANGUAGE_GERMAN_BELGIUM: + case wxLANGUAGE_GERMAN_LIECHTENSTEIN: + case wxLANGUAGE_GERMAN_LUXEMBOURG: + case wxLANGUAGE_GERMAN_SWISS: + return wxLANGUAGE_GERMAN; + + //variants of wxLANGUAGE_FRENCH + case wxLANGUAGE_FRENCH_BELGIAN: + case wxLANGUAGE_FRENCH_CANADIAN: + case wxLANGUAGE_FRENCH_LUXEMBOURG: + case wxLANGUAGE_FRENCH_MONACO: + case wxLANGUAGE_FRENCH_SWISS: + return wxLANGUAGE_FRENCH; + + //variants of wxLANGUAGE_DUTCH + case wxLANGUAGE_DUTCH_BELGIAN: + return wxLANGUAGE_DUTCH; + + //variants of wxLANGUAGE_ITALIAN + case wxLANGUAGE_ITALIAN_SWISS: + return wxLANGUAGE_ITALIAN; + + //variants of wxLANGUAGE_CHINESE_SIMPLIFIED + case wxLANGUAGE_CHINESE: + case wxLANGUAGE_CHINESE_SINGAPORE: + return wxLANGUAGE_CHINESE_SIMPLIFIED; + + //variants of wxLANGUAGE_CHINESE_TRADITIONAL + case wxLANGUAGE_CHINESE_TAIWAN: + case wxLANGUAGE_CHINESE_HONGKONG: + case wxLANGUAGE_CHINESE_MACAU: + return wxLANGUAGE_CHINESE_TRADITIONAL; + + //variants of wxLANGUAGE_RUSSIAN + case wxLANGUAGE_RUSSIAN_UKRAINE: + return wxLANGUAGE_RUSSIAN; + + //variants of wxLANGUAGE_SPANISH + case wxLANGUAGE_SPANISH_ARGENTINA: + case wxLANGUAGE_SPANISH_BOLIVIA: + case wxLANGUAGE_SPANISH_CHILE: + case wxLANGUAGE_SPANISH_COLOMBIA: + case wxLANGUAGE_SPANISH_COSTA_RICA: + case wxLANGUAGE_SPANISH_DOMINICAN_REPUBLIC: + case wxLANGUAGE_SPANISH_ECUADOR: + case wxLANGUAGE_SPANISH_EL_SALVADOR: + case wxLANGUAGE_SPANISH_GUATEMALA: + case wxLANGUAGE_SPANISH_HONDURAS: + case wxLANGUAGE_SPANISH_MEXICAN: + case wxLANGUAGE_SPANISH_MODERN: + case wxLANGUAGE_SPANISH_NICARAGUA: + case wxLANGUAGE_SPANISH_PANAMA: + case wxLANGUAGE_SPANISH_PARAGUAY: + case wxLANGUAGE_SPANISH_PERU: + case wxLANGUAGE_SPANISH_PUERTO_RICO: + case wxLANGUAGE_SPANISH_URUGUAY: + case wxLANGUAGE_SPANISH_US: + case wxLANGUAGE_SPANISH_VENEZUELA: + return wxLANGUAGE_SPANISH; + + //variants of wxLANGUAGE_SWEDISH + case wxLANGUAGE_SWEDISH_FINLAND: + return wxLANGUAGE_SWEDISH; + + //case wxLANGUAGE_CZECH: + //case wxLANGUAGE_FINNISH: + //case wxLANGUAGE_GREEK: + //case wxLANGUAGE_JAPANESE: + //case wxLANGUAGE_POLISH: + //case wxLANGUAGE_SLOVENIAN: + //case wxLANGUAGE_HUNGARIAN: + //case wxLANGUAGE_PORTUGUESE: + //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: + //case wxLANGUAGE_KOREAN: + + //variants of wxLANGUAGE_ARABIC + case wxLANGUAGE_ARABIC_ALGERIA: + case wxLANGUAGE_ARABIC_BAHRAIN: + case wxLANGUAGE_ARABIC_EGYPT: + case wxLANGUAGE_ARABIC_IRAQ: + case wxLANGUAGE_ARABIC_JORDAN: + case wxLANGUAGE_ARABIC_KUWAIT: + case wxLANGUAGE_ARABIC_LEBANON: + case wxLANGUAGE_ARABIC_LIBYA: + case wxLANGUAGE_ARABIC_MOROCCO: + case wxLANGUAGE_ARABIC_OMAN: + case wxLANGUAGE_ARABIC_QATAR: + case wxLANGUAGE_ARABIC_SAUDI_ARABIA: + case wxLANGUAGE_ARABIC_SUDAN: + case wxLANGUAGE_ARABIC_SYRIA: + case wxLANGUAGE_ARABIC_TUNISIA: + case wxLANGUAGE_ARABIC_UAE: + case wxLANGUAGE_ARABIC_YEMEN: + return wxLANGUAGE_ARABIC; + + //variants of wxLANGUAGE_ENGLISH_UK + case wxLANGUAGE_ENGLISH_AUSTRALIA: + case wxLANGUAGE_ENGLISH_NEW_ZEALAND: + case wxLANGUAGE_ENGLISH_TRINIDAD: + case wxLANGUAGE_ENGLISH_CARIBBEAN: + case wxLANGUAGE_ENGLISH_JAMAICA: + case wxLANGUAGE_ENGLISH_BELIZE: + case wxLANGUAGE_ENGLISH_EIRE: + case wxLANGUAGE_ENGLISH_SOUTH_AFRICA: + case wxLANGUAGE_ENGLISH_ZIMBABWE: + case wxLANGUAGE_ENGLISH_BOTSWANA: + case wxLANGUAGE_ENGLISH_DENMARK: + return wxLANGUAGE_ENGLISH_UK; + + default: + return language; + } +} + + +inline +void exchangeEscapeChars(wxString& data) +{ + wxString output; + + const wxChar* input = data.c_str(); + + wxChar value; + while ((value = *input) != wxChar(0)) + { + //read backslash + if (value == wxChar('\\')) + { + //read next character + ++input; + if ((value = *input) == wxChar(0)) + break; + + switch (value) + { + case wxChar('\\'): + output += wxChar('\\'); + break; + case wxChar('n'): + output += wxChar('\n'); + break; + case wxChar('t'): + output += wxChar('\t'); + break; + case wxChar('\"'): + output += wxChar('\"'); + break; + default: + output += value; + } + } + else + output += value; + + ++input; + } + data = output; +} + + +//workaround to get a FILE* from a unicode filename in a portable way +class UnicodeFileReader +{ +public: + UnicodeFileReader(const wxString& filename) : + inputFile(NULL) + { + wxFFile dummyFile(filename, wxT("rb")); + if (dummyFile.IsOpened()) + { + inputFile = dummyFile.fp(); + dummyFile.Detach(); + } + } + + ~UnicodeFileReader() + { + if (inputFile != NULL) + fclose(inputFile); + } + + bool isOkay() + { + return inputFile != NULL; + } + + bool getNextLine(wxString& line) + { + std::string output; + + while (true) + { + const int c = fgetc(inputFile); + if (c == EOF) + return false; + else if (c == 0xD) + { + //Delimiter: + //---------- + //Linux: 0xA \n + //Mac: 0xD \r + //Win: 0xD 0xA \r\n <- language files are in Windows format + + fgetc(inputFile); //discard the 0xA character + + line = wxString::FromUTF8(output.c_str(), output.length()); + return true; + } + output += static_cast<char>(c); + } + } + +private: + FILE* inputFile; +}; + + +void loadTranslation(const wxString& filename, Translation& trans) //empty translation on error +{ + trans.clear(); + + UnicodeFileReader langFile(ffs3::getResourceDir() + wxT("Languages") + ffs3::zToWx(common::FILE_NAME_SEPARATOR) + filename); + if (langFile.isOkay()) + { + int rowNumber = 0; + wxString original; + wxString tmpString; + while (langFile.getNextLine(tmpString)) + { + exchangeEscapeChars(tmpString); + + if (rowNumber++ % 2 == 0) + original = tmpString; + else + { + const wxString& translation = tmpString; + + if (!original.empty() && !translation.empty()) + trans.insert(std::make_pair(original, translation)); + } + } + } +} +} + + +void ffs3::setLanguage(int language) +{ + static class StaticInit + { + public: + StaticInit() : loc(wxLANGUAGE_DEFAULT) //wxLocale: we need deferred initialization, sigh... + { + //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale + const lconv* localInfo = ::localeconv(); + + //actually these two parameters are language dependent, but we take system setting to handle all kinds of language derivations + THOUSANDS_SEPARATOR = wxString::FromUTF8(localInfo->thousands_sep); + DECIMAL_POINT = wxString::FromUTF8(localInfo->decimal_point); + + // why not working? + // THOUSANDS_SEPARATOR = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).thousands_sep(); + // DECIMAL_POINT = std::use_facet<std::numpunct<wchar_t> >(std::locale("")).decimal_point(); + } + private: + wxLocale loc; //required for RTL language support (and nothing else) + } dummy; + + activeLanguage = language; + + //default: english + wxString languageFile; + + //(try to) retrieve language filename + const int mappedLanguage = mapLanguageDialect(language); + for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::get().begin(); i != LocalizationInfo::get().end(); ++i) + if (i->languageID == mappedLanguage) + { + languageFile = i->languageFile; + break; + } + + //load language file into buffer + activeTranslation.clear(); + if (!languageFile.empty()) + { + loadTranslation(languageFile, activeTranslation); //empty translation on error + if (activeTranslation.empty()) + { + wxMessageBox(wxString(_("Error reading file:")) + wxT(" \"") + languageFile + wxT("\""), _("Error"), wxOK | wxICON_ERROR); + activeLanguage = wxLANGUAGE_ENGLISH; //reset to english language to show this error just once + } + } + else + ; //if languageFile is empty texts will be english per default +} + + +int ffs3::getLanguage() +{ + return activeLanguage; +} + + +int ffs3::retrieveSystemLanguage() +{ + return wxLocale::GetSystemLanguage(); +} + + + + +/* +typedef Zbase<wchar_t> Wstring; + +const Wstring TK_TERNARY_QUEST = L"?"; +const Wstring TK_TERNARY_COLON = L":"; +const Wstring TK_OR = L"||"; +const Wstring TK_AND = L"&&"; +const Wstring TK_EQUAL = L"=="; +const Wstring TK_NOT_EQUAL = L"!="; +const Wstring TK_LESS = L"<"; +const Wstring TK_LESS_EQUAL = L"<="; +const Wstring TK_GREATER = L">"; +const Wstring TK_GREATER_EQUAL = L">="; +const Wstring TK_MODULUS = L"%"; +const Wstring TK_N = L"n"; +const Wstring TK_BRACKET_LEFT = L"("; +const Wstring TK_BRACKET_RIGHT = L")"; +const Wstring TK_FORM_COUNT = L"nplurals="; +const Wstring TK_PHRASE_BEGIN = L"plural="; + + +template <class T> +struct Expr +{ + typedef T ValueType; + virtual ValueType eval() const = 0; +}; + + +template <class In, class Out, class BiOp> +struct GenericBiExp : public Out +{ + GenericBiExp(const In& lhs, const In& rhs, BiOp biop) : lhs_(lhs), rhs_(rhs), biop_(biop) {} + virtual typename Out::ValueType eval() const { return biop_(lhs_.eval(), rhs_.eval()); } + const In& lhs_; + const In& rhs_; + BiOp biop_; +}; + +template <class Out, class In, class BiOp> +inline +GenericBiExp<In, Out, BiOp> makeBiExp(const In& lhs, const In& rhs, BiOp biop) +{ + return GenericBiExp<In, Out, BiOp>(lhs, rhs, biop); +} + +template <class Out> +struct TernaryExp : public Out +{ + TernaryExp(const Expr<bool>& ifExp, const Out& thenExp, const Out& elseExp) : ifExp_(ifExp), thenExp_(thenExp), elseExp_(elseExp) {} + virtual typename Out::ValueType eval() const { return ifExp_.eval() ? thenExp_.eval() : elseExp_.eval(); } + const Expr<bool>& ifExp_; + const Out& thenExp_; + const Out& elseExp_; +}; + +struct LiteralNumberEx : public Expr<int> +{ + LiteralNumberEx(int n) : n_(n) {} + virtual ValueType eval() const { return n_; } + int n_; +}; + +struct NumberN : public Expr<int> +{ + NumberN(int& n) : n_(n) {} + virtual ValueType eval() const { return n_; } + int& n_; +}; + + +class PluralForm +{ +public: + struct ParserError {}; + + PluralForm(const Wstring& phrase) //.po format,e.g.: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) + { + Wstring tmp; + std::remove_copy_if(phrase.begin(), phrase.end(), std::back_inserter(tmp), std::iswspace); + + size_t pos = tmp.find(TK_FORM_COUNT); + if (pos == Wstring::npos) + throw ParserError(); + + count = tmp.substr(pos + TK_FORM_COUNT.size()).toNumber<int>(); + + pos = tmp.find(TK_PHRASE_BEGIN); + if (pos == Wstring::npos) + throw ParserError(); + + expr = &parseNumber(tmp.substr(pos + TK_PHRASE_BEGIN.size())); + } + + int formCount() const { return count; } + + int getForm(int n) const { n_ = n ; return expr->eval(); } + +private: + const Expr<bool>& parseBool(const Wstring& phrase) + { + Wstring part1, part2; + if (trySplit(phrase, TK_OR, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseBool(part1), parseBool(part2), std::logical_or<bool>())); + else if (trySplit(phrase, TK_AND, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseBool(part1), parseBool(part2), std::logical_and<bool>())); + else if (trySplit(phrase, TK_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::equal_to<int>())); + else if (trySplit(phrase, TK_NOT_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::not_equal_to<int>())); + else if (trySplit(phrase, TK_LESS, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::less<int>())); + else if (trySplit(phrase, TK_LESS_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::less_equal<int>())); + else if (trySplit(phrase, TK_GREATER, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::greater<int>())); + else if (trySplit(phrase, TK_GREATER_EQUAL, part1, part2)) + return manageObj(makeBiExp<Expr<bool> >(parseNumber(part1), parseNumber(part2), std::greater_equal<int>())); + else + throw ParserError(); + } + + const Expr<int>& parseNumber(const Wstring& phrase) + { + Wstring part1, part2; + + + if (trySplit(phrase, TK_TERNARY_QUEST, part1, part2, TO_LEFT)) + { + Wstring part3; + if (!trySplit(Wstring(part2), TK_TERNARY_COLON, part2, part3, TO_LEFT)) + throw ParserError(); + return manageObj(TernaryExp<Expr<int> >(parseBool(part1), parseNumber(part2), parseNumber(part3))); + } + else if (trySplit(phrase, TK_MODULUS, part1, part2)) + return manageObj(makeBiExp<Expr<int> >(parseNumber(part1), parseNumber(part2), std::modulus<int>())); + else if (phrase == TK_N) + return manageObj(NumberN(n_)); + else //expect literal number + { + if (std::find_if(phrase.begin(), phrase.end(), std::not1(std::ptr_fun(std::iswdigit))) != phrase.end()) + throw ParserError(); + return manageObj(LiteralNumberEx(phrase.toNumber<int>())); + } + } + + enum Associativity + { + TO_LEFT, + TO_RIGHT + }; + static bool trySplit(const Wstring& phrase, const Wstring& delimiter, Wstring& part1, Wstring& part2, Associativity assoc = TO_RIGHT) + { + size_t pos = assoc == TO_LEFT ? + phrase.find(delimiter) : //reverse [!] + phrase.rfind(delimiter); + if (pos == Wstring::npos) + return false; + + part1 = phrase.substr(0, pos); + part2 = phrase.substr(pos + delimiter.size()); + return true; + } + + template <class T> + const T& manageObj(const T& obj) + { + dump.push_back(obj); + return *boost::any_cast<T>(&dump.back()); + } + + int count; + const Expr<int>* expr; + mutable int n_; + + std::list<boost::any> dump; //manage polymorphc object lifetimes +}; + + + PluralForm dummy(L"nplurals=3; plural=n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 > 20) ? 1 : 2"); + int ddf = dummy.formCount(); + int kddf = dummy.getForm(0); +*/ + + + +wxString ffs3::translate(const wxString& original) //translate into currently selected language +{ + //look for translation in buffer table + const Translation::const_iterator i = activeTranslation.find(original); + if (i != activeTranslation.end()) + return i->second.c_str(); + + //fallback + return original; +} diff --git a/shared/pch.h b/shared/pch.h new file mode 100644 index 00000000..eb95fd50 --- /dev/null +++ b/shared/pch.h @@ -0,0 +1,119 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// +#ifndef FFS_PRECOMPILED_HEADER +#define FFS_PRECOMPILED_HEADER + +//pay attention when using this file: might cause issues! +#ifdef NDEBUG +#error do NOT use in release build! +#endif + +#ifdef _MSC_VER +#pragma warning(disable:4996) //"warning C4996: 'std::copy': Function call with parameters that may be unsafe" +#endif + +//##################################################### +// basic wxWidgets headers +#ifndef WX_PRECOMP +#define WX_PRECOMP +#endif + +#include <wx/wxprec.h> + +//##################################################### +// #include other rarely changing headers here + +//STL headers +#include <string> +#include <vector> +#include <set> +#include <map> +#include <queue> +#include <deque> +#include <stack> +#include <list> +#include <algorithm> +#include <functional> +#include <iterator> +#include <numeric> +#include <memory> +#include <utility> +#include <fstream> +#include <iostream> +#include <sstream> +#include <new> +#include <stdexcept> + +//other wxWidgets headers +#include <wx/log.h> +#include <wx/grid.h> +#include <wx/animate.h> +#include <wx/app.h> +#include <wx/arrstr.h> +#include <wx/bitmap.h> +#include <wx/bmpbuttn.h> +#include <wx/button.h> +#include <wx/checkbox.h> +#include <wx/choice.h> +#include <wx/clipbrd.h> +#include <wx/cmdline.h> +#include <wx/colour.h> +#include <wx/config.h> +#include <wx/dc.h> +#include <wx/dialog.h> +#include <wx/dir.h> +#include <wx/dnd.h> +#include <wx/file.h> +#include <wx/filename.h> +#include <wx/filepicker.h> +#include <wx/font.h> +#include <wx/frame.h> +#include <wx/gauge.h> +#include <wx/gdicmn.h> +#include <wx/grid.h> +#include <wx/hyperlink.h> +#include <wx/icon.h> +#include <wx/image.h> +#include <wx/intl.h> +#include <wx/log.h> +#include <wx/menu.h> +#include <wx/msgdlg.h> +#include <wx/panel.h> +#include <wx/radiobut.h> +#include <wx/settings.h> +#include <wx/sizer.h> +#include <wx/statbmp.h> +#include <wx/statbox.h> +#include <wx/statline.h> +#include <wx/stattext.h> +#include <wx/stdpaths.h> +#include <wx/stopwatch.h> +#include <wx/stream.h> +#include <wx/string.h> +#include <wx/textctrl.h> +#include <wx/thread.h> +#include <wx/utils.h> +#include <wx/wfstream.h> +#include <wx/zipstrm.h> +#include <wx/scrolwin.h> +#include <wx/notebook.h> +#include <wx/help.h> +#include <wx/event.h> + +//other +#include "../shared/tinyxml/tinyxml.h" +#include <sys/stat.h> + +//Boost +#include <boost/shared_ptr.hpp> +#include <boost/scoped_array.hpp> + +#ifdef __WXMSW__ +#include <wx/msw/wrapwin.h> //includes "windows.h" +#endif //__WXMSW__ + +#endif //FFS_PRECOMPILED_HEADER diff --git a/shared/perf.h b/shared/perf.h index 1ad4650d..641eee2b 100644 --- a/shared/perf.h +++ b/shared/perf.h @@ -8,13 +8,15 @@ #define DEBUG_PERF_HEADER #include <sstream> + +#ifdef __WXWINDOWS__ +#include <wx/msw/wrapwin.h> //includes "windows.h" +#else //#define WIN32_LEAN_AND_MEAN -> not in a header -/* #include <windows.h> #undef max #undef min -*/ -#include <wx/msw/wrapwin.h> //includes "windows.h" +#endif #ifdef __MINGW32__ @@ -28,37 +30,48 @@ class CpuTimer public: class TimerError {}; - DEPRECATED(CpuTimer()) : resultWasShown(false), startTime(), frequency() + DEPRECATED(CpuTimer()) : frequency(), startTime(), resultShown(false) { + SetThreadAffinity dummy; if (!::QueryPerformanceFrequency(&frequency)) throw TimerError(); if (!::QueryPerformanceCounter (&startTime)) throw TimerError(); } ~CpuTimer() { - if (!resultWasShown) + if (!resultShown) showResult(); } void showResult() { + SetThreadAffinity dummy; LARGE_INTEGER currentTime = {}; if (!::QueryPerformanceCounter(¤tTime)) throw TimerError(); - const long delta = 1000.0 * (currentTime.QuadPart - startTime.QuadPart) / frequency.QuadPart; + const long delta = static_cast<long>(1000.0 * (currentTime.QuadPart - startTime.QuadPart) / frequency.QuadPart); std::ostringstream ss; ss << delta << " ms"; ::MessageBoxA(NULL, ss.str().c_str(), "Timer", 0); - resultWasShown = true; + resultShown = true; if (!::QueryPerformanceCounter(&startTime)) throw TimerError(); //don't include call to MessageBox()! } private: - bool resultWasShown; - LARGE_INTEGER startTime; + class SetThreadAffinity + { + public: + SetThreadAffinity() : oldmask(::SetThreadAffinityMask(::GetCurrentThread(), 1)) { if (oldmask == 0) throw TimerError(); } + ~SetThreadAffinity() { ::SetThreadAffinityMask(::GetCurrentThread(), oldmask); } + private: + const DWORD_PTR oldmask; + }; + LARGE_INTEGER frequency; + LARGE_INTEGER startTime; + bool resultShown; }; //two macros for quick performance measurements diff --git a/shared/privilege.cpp b/shared/privilege.cpp index 00eb2855..5cb664e1 100644 --- a/shared/privilege.cpp +++ b/shared/privilege.cpp @@ -1,7 +1,6 @@ #include "privilege.h" -#include <wx/intl.h> #include "system_func.h" -//#include <boost/shared_ptr.hpp> +#include "i18n.h" #include "loki/ScopeGuard.h" using namespace ffs3; @@ -14,7 +13,7 @@ Privileges& Privileges::getInstance() } -bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError() +bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw (FileError) { HANDLE hToken = NULL; if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, @@ -58,7 +57,7 @@ bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError() } -void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError() +void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw (FileError) { HANDLE hToken = NULL; if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, diff --git a/shared/privilege.h b/shared/privilege.h index c5ec99c7..0c5d5f0c 100644 --- a/shared/privilege.h +++ b/shared/privilege.h @@ -15,7 +15,7 @@ class Privileges public: static Privileges& getInstance(); - void ensureActive(LPCTSTR privilege) //throw FileError() + void ensureActive(LPCTSTR privilege) //throw (FileError) { if (activePrivileges.find(privilege) != activePrivileges.end()) return; //privilege already active @@ -45,8 +45,8 @@ private: catch(...) {} } - static bool privilegeIsActive(LPCTSTR privilege); //throw FileError() - static void setPrivilege(LPCTSTR privilege, bool enable); //throw FileError() + static bool privilegeIsActive(LPCTSTR privilege); //throw (FileError) + static void setPrivilege(LPCTSTR privilege, bool enable); //throw (FileError) typedef std::map<Zstring, bool> PrivBuffType; //bool: enabled by this application diff --git a/shared/recycler.cpp b/shared/recycler.cpp index 4e9ce9a3..a2b080c9 100644 --- a/shared/recycler.cpp +++ b/shared/recycler.cpp @@ -5,10 +5,10 @@ // ************************************************************************** // #include "recycler.h" -#include "string_conv.h" -#include <wx/intl.h> #include <stdexcept> #include <iterator> +#include "i18n.h" +#include "string_conv.h" #ifdef FFS_WIN #include "dll_loader.h" diff --git a/shared/resolve_path.cpp b/shared/resolve_path.cpp new file mode 100644 index 00000000..b17c3fb5 --- /dev/null +++ b/shared/resolve_path.cpp @@ -0,0 +1,319 @@ +#include "resolve_path.h" +#include <boost/scoped_array.hpp> +#include <wx/utils.h> +#include <wx/datetime.h> +#include "string_conv.h" +#include "system_constants.h" +#include "loki/ScopeGuard.h" + +#ifdef FFS_WIN +#include "dll_loader.h" +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "long_path_prefix.h" + +#elif defined FFS_LINUX +#include <map> +#include "file_traverser.h" +#include "stdlib.h" +#endif + +using namespace ffs3; +using namespace common; + + +namespace +{ +#ifdef FFS_WIN +Zstring resolveRelativePath(const Zstring& relativeName, DWORD proposedBufferSize = 1000) +{ + boost::scoped_array<Zchar> fullPath(new Zchar[proposedBufferSize]); + const DWORD rv = ::GetFullPathName( + applyLongPathPrefix(relativeName).c_str(), //__in LPCTSTR lpFileName, + proposedBufferSize, //__in DWORD nBufferLength, + fullPath.get(), //__out LPTSTR lpBuffer, + NULL); //__out LPTSTR *lpFilePart + if (rv == 0 || rv == proposedBufferSize) + //ERROR! Don't do anything + return relativeName; + if (rv > proposedBufferSize) + return resolveRelativePath(relativeName, rv); + + return fullPath.get(); +} + +#elif defined FFS_LINUX +Zstring resolveRelativePath(const Zstring& relativeName) //additional: resolves symbolic links!!! +{ + char absolutePath[PATH_MAX + 1]; + if (::realpath(relativeName.c_str(), absolutePath) == NULL) + //ERROR! Don't do anything + return relativeName; + + return Zstring(absolutePath); +} +#endif + + +bool replaceMacro(wxString& macro) //macro without %-characters, return true if replaced successfully +{ + if (macro.IsEmpty()) + return false; + + //there are equally named environment variables %TIME%, %DATE% existing, so replace these first! + if (macro.CmpNoCase(wxT("time")) == 0) + { + macro = wxDateTime::Now().FormatISOTime(); + macro.Replace(wxT(":"), wxT("")); + return true; + } + + if (macro.CmpNoCase(wxT("date")) == 0) + { + macro = wxDateTime::Now().FormatISODate(); + return true; + } + + if (macro.CmpNoCase(wxT("weekday")) == 0) + { + macro = wxDateTime::Now().Format(wxT("%A")); + return true; + } + + if (macro.CmpNoCase(wxT("month")) == 0) + { + macro = wxDateTime::Now().Format(wxT("%B")); + return true; + } + + if (macro.CmpNoCase(wxT("week")) == 0) + { + macro = wxDateTime::Now().Format(wxT("%U")); + return true; + } + + if (macro.CmpNoCase(wxT("year")) == 0) + { + macro = wxDateTime::Now().Format(wxT("%Y")); + return true; + } + + //try to apply environment variables + wxString envValue; + if (wxGetEnv(macro, &envValue)) + { + macro = envValue; + + //some postprocessing: + macro.Trim(true); //remove leading, trailing blanks + macro.Trim(false); // + + //remove leading, trailing double-quotes + if (macro.StartsWith(wxT("\"")) && + macro.EndsWith(wxT("\"")) && + macro.length() >= 2) + macro = wxString(macro.c_str() + 1, macro.length() - 2); + return true; + } + + return false; +} + + +void expandMacros(wxString& text) +{ + const wxChar SEPARATOR = '%'; + + if (text.Find(SEPARATOR) != wxNOT_FOUND) + { + wxString prefix = text.BeforeFirst(SEPARATOR); + wxString postfix = text.AfterFirst(SEPARATOR); + if (postfix.Find(SEPARATOR) != wxNOT_FOUND) + { + wxString potentialMacro = postfix.BeforeFirst(SEPARATOR); + wxString rest = postfix.AfterFirst(SEPARATOR); //text == prefix + SEPARATOR + potentialMacro + SEPARATOR + rest + + if (replaceMacro(potentialMacro)) + { + expandMacros(rest); + text = prefix + potentialMacro + rest; + } + else + { + rest = SEPARATOR + rest; + expandMacros(rest); + text = prefix + SEPARATOR + potentialMacro + rest; + } + } + } +} + + +#ifdef FFS_LINUX +class TraverseMedia : public ffs3::TraverseCallback +{ +public: + typedef std::map<Zstring, Zstring> DeviceList; //device name -> device path mapping + + TraverseMedia(DeviceList& devices) : devices_(devices) {} + + virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {} + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {} + virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) + { + devices_.insert(std::make_pair(shortName, fullName)); + return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs + } + virtual void onError(const wxString& errorText) {} + +private: + DeviceList& devices_; +}; +#endif + + +Zstring getVolumePath(const Zstring& volumeName) //empty string on error +{ +#ifdef FFS_WIN + const size_t volGuidSize = 10000; + boost::scoped_array<wchar_t> volGuid(new wchar_t[volGuidSize]); + + HANDLE hVol = ::FindFirstVolume(volGuid.get(), volGuidSize); + if (hVol != INVALID_HANDLE_VALUE) + { + Loki::ScopeGuard dummy = Loki::MakeGuard(::FindVolumeClose, hVol); + (void)dummy; + + do + { + const size_t volNameSize = MAX_PATH + 1; + boost::scoped_array<wchar_t> volName(new wchar_t[volNameSize]); + + if (::GetVolumeInformation(volGuid.get(), //__in_opt LPCTSTR lpRootPathName, + volName.get(), //__out LPTSTR lpVolumeNameBuffer, + volNameSize, //__in DWORD nVolumeNameSize, + NULL, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + NULL, //__out_opt LPDWORD lpFileSystemFlags, + NULL, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + { + if (EqualFilename()(volumeName, Zstring(volName.get()))) + { + //GetVolumePathNamesForVolumeName is not available for Windows 2000! + typedef BOOL (WINAPI *GetVolumePathNamesForVolumeNameWFunc)(LPCWSTR lpszVolumeName, + LPWCH lpszVolumePathNames, + DWORD cchBufferLength, + PDWORD lpcchReturnLength); + + static const GetVolumePathNamesForVolumeNameWFunc getVolumePathNamesForVolumeName = + util::getDllFun<GetVolumePathNamesForVolumeNameWFunc>(L"kernel32.dll", "GetVolumePathNamesForVolumeNameW"); + + if (getVolumePathNamesForVolumeName != NULL) + { + const DWORD volPathSize = 10000; + boost::scoped_array<wchar_t> volPath(new wchar_t[volPathSize]); + + DWORD returnedLen = 0; + if (getVolumePathNamesForVolumeName(volGuid.get(), //__in LPCTSTR lpszVolumeName, + volPath.get(), //__out LPTSTR lpszVolumePathNames, + volPathSize, //__in DWORD cchBufferLength, + &returnedLen)) //__out PDWORD lpcchReturnLength + { + return volPath.get(); //return first path name in double-null terminated list! + } + } + return volGuid.get(); //GUID looks ugly, but should be working correctly + } + } + } + while (::FindNextVolume(hVol, volGuid.get(), volGuidSize)); + } + +#elif defined FFS_LINUX + //due to the naming convention on Linux /media/<volume name> this function is not that useful, but... + + TraverseMedia::DeviceList deviceList; + + TraverseMedia traverser(deviceList); + traverseFolder("/media", false, traverser); //traverse one level + + TraverseMedia::DeviceList::const_iterator iter = deviceList.find(volumeName); + if (iter != deviceList.end()) + return iter->second; +#endif + return Zstring(); +} + + +void expandVolumeName(Zstring& text) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder +{ + Zstring before; + Zstring volname; + Zstring after; + + size_t posStart = text.find(Zstr("[")); + if (posStart != Zstring::npos) + { + size_t posEnd = text.find(Zstr("]"), posStart); + if (posEnd != Zstring::npos) + { + before = Zstring(text.c_str(), posStart); + volname = Zstring(text.c_str() + posStart + 1, posEnd - posStart - 1); + after = Zstring(text.c_str() + posEnd + 1); + + if (after.StartsWith(Zstr(":"))) + after = after.AfterFirst(Zstr(':')); + if (after.StartsWith(Zstring() + FILE_NAME_SEPARATOR)) + after = after.AfterFirst(FILE_NAME_SEPARATOR); + } + } + + if (volname.empty()) + return; + + Zstring volPath = getVolumePath(volname); //return empty string on error + if (volPath.empty()) + return; + + if (!volPath.EndsWith(FILE_NAME_SEPARATOR)) + volPath += FILE_NAME_SEPARATOR; + + text = before + volPath + after; +} +} + + +Zstring ffs3::getFormattedDirectoryName(const Zstring& dirname) +{ + //Formatting is needed since functions expect the directory to end with '\' to be able to split the relative names. + //note: don't combine directory formatting with wxFileName, as it doesn't respect //?/ - prefix! + + wxString dirnameTmp = zToWx(dirname); + expandMacros(dirnameTmp); + + Zstring output = wxToZ(dirnameTmp); + + expandVolumeName(output); + + output.Trim(); + + if (output.empty()) //an empty string will later be returned as "\"; this is not desired + return Zstring(); + + /* + resolve relative names; required by: + WINDOWS: + - \\?\-prefix which needs absolute names + - Volume Shadow Copy: volume name needs to be part of each filename + - file icon buffer (at least for extensions that are actually read from disk, e.g. "exe") + - ::SHFileOperation(): Using relative path names is not thread safe + WINDOWS/LINUX: + - detection of dependent directories, e.g. "\" and "C:\test" + */ + output = resolveRelativePath(output); + + if (!output.EndsWith(FILE_NAME_SEPARATOR)) + output += FILE_NAME_SEPARATOR; + + return output; +} diff --git a/shared/resolve_path.h b/shared/resolve_path.h new file mode 100644 index 00000000..0bd4b0d2 --- /dev/null +++ b/shared/resolve_path.h @@ -0,0 +1,19 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// +#ifndef RESOLVE_PATH_H_INCLUDED +#define RESOLVE_PATH_H_INCLUDED + +#include "zstring.h" + + +namespace ffs3 +{ +Zstring getFormattedDirectoryName(const Zstring& dirname); +} + + +#endif // RESOLVE_PATH_H_INCLUDED diff --git a/shared/serialize.cpp b/shared/serialize.cpp index f71207be..719d0861 100644 --- a/shared/serialize.cpp +++ b/shared/serialize.cpp @@ -5,12 +5,12 @@ // ************************************************************************** // #include "serialize.h" -#include <wx/intl.h> +#include "i18n.h" using namespace util; -void ReadInputStream::throwReadError() const //throw FileError() +void ReadInputStream::throwReadError() const //throw (FileError) { throw ffs3::FileError(wxString(_("Error reading from synchronization database:")) + wxT(" \n") + wxT("\"") + errorObjName_ + wxT("\"")); @@ -33,7 +33,7 @@ ReadInputStream::CharArray ReadInputStream::readArrayC() const //-------------------------------------------------------------------------------------------------------- -void WriteOutputStream::throwWriteError() const //throw FileError() +void WriteOutputStream::throwWriteError() const //throw (FileError) { throw ffs3::FileError(wxString(_("Error writing to synchronization database:")) + wxT(" \n") + wxT("\"") + errorObjName_ + wxT("\"")); diff --git a/shared/serialize.h b/shared/serialize.h index 8da0a449..db1848d5 100644 --- a/shared/serialize.h +++ b/shared/serialize.h @@ -27,18 +27,18 @@ Zstring readString(wxInputStream& stream); void writeString(wxOutputStream& stream, const Zstring& str); -class ReadInputStream //throw FileError() +class ReadInputStream //throw (FileError) { protected: ReadInputStream(wxInputStream& stream, const wxString& errorObjName) : stream_(stream), errorObjName_(errorObjName) {} template <class T> - T readNumberC() const; //throw FileError(), checked read operation + T readNumberC() const; //throw (FileError), checked read operation - Zstring readStringC() const; //throw FileError(), checked read operation + Zstring readStringC() const; //throw (FileError), checked read operation typedef boost::shared_ptr<std::vector<char> > CharArray; //there's no guarantee std::string has a ref-counted implementation... so use this "thing" - CharArray readArrayC() const; //throw FileError() + CharArray readArrayC() const; //throw (FileError) void check() const; @@ -49,22 +49,22 @@ protected: private: wxInputStream& stream_; - void throwReadError() const; //throw FileError() + void throwReadError() const; //throw (FileError) const wxString& errorObjName_; //used for error text only }; -class WriteOutputStream //throw FileError() +class WriteOutputStream //throw (FileError) { protected: WriteOutputStream(const wxString& errorObjName, wxOutputStream& stream) : stream_(stream), errorObjName_(errorObjName) {} template <class T> - void writeNumberC(T number) const; //throw FileError(), checked write operation + void writeNumberC(T number) const; //throw (FileError), checked write operation - void writeStringC(const Zstring& str) const; //throw FileError(), checked write operation + void writeStringC(const Zstring& str) const; //throw (FileError), checked write operation - void writeArrayC(const std::vector<char>& buffer) const; //throw FileError() + void writeArrayC(const std::vector<char>& buffer) const; //throw (FileError) void check() const; @@ -75,7 +75,7 @@ protected: private: wxOutputStream& stream_; - void throwWriteError() const; //throw FileError() + void throwWriteError() const; //throw (FileError) const wxString& errorObjName_; //used for error text only! }; diff --git a/shared/shadow.cpp b/shared/shadow.cpp index 9dab1494..150a8bbf 100644 --- a/shared/shadow.cpp +++ b/shared/shadow.cpp @@ -6,8 +6,8 @@ // #include "shadow.h" #include <wx/msw/wrapwin.h> //includes "windows.h" -#include <wx/intl.h> #include "system_constants.h" +#include "i18n.h" #include "dll_loader.h" #include <stdexcept> #include "assert_static.h" diff --git a/shared/standard_paths.cpp b/shared/standard_paths.cpp index 6f723ef7..e4f87dd6 100644 --- a/shared/standard_paths.cpp +++ b/shared/standard_paths.cpp @@ -6,12 +6,12 @@ // #include "standard_paths.h" #include <wx/stdpaths.h> -#include <wx/filename.h> #include "system_constants.h" #include "string_conv.h" using namespace ffs3; + bool ffs3::isPortableVersion() { #ifdef FFS_WIN @@ -25,7 +25,7 @@ bool ffs3::isPortableVersion() const wxString& ffs3::getBinaryDir() { - static wxString instance = wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath() + zToWx(common::FILE_NAME_SEPARATOR); + static wxString instance = zToWx(wxToZ(wxStandardPaths::Get().GetExecutablePath()).BeforeLast(common::FILE_NAME_SEPARATOR) + common::FILE_NAME_SEPARATOR); return instance; } diff --git a/shared/string_conv.h b/shared/string_conv.h index 85181d11..24cb521e 100644 --- a/shared/string_conv.h +++ b/shared/string_conv.h @@ -22,8 +22,8 @@ Zstring wxToZ(const wxChar* str); Zstring wxToZ(wxChar ch); - - +wxString zxToWx(const zxString& str); +zxString wxToZx(const wxString& str); @@ -105,6 +105,20 @@ Zstring wxToZ(wxChar ch) { return wxToZ(wxString(ch)); } + + +inline +wxString zxToWx(const zxString& str) +{ + return wxString(str.c_str(), str.length()); +} + + +inline +zxString wxToZx(const wxString& str) +{ + return zxString(str.c_str(), str.length()); +} } #endif // STRINGCONV_H_INCLUDED diff --git a/shared/symlink_target.h b/shared/symlink_target.h index 6dec85a0..200c76d8 100644 --- a/shared/symlink_target.h +++ b/shared/symlink_target.h @@ -10,9 +10,9 @@ #include "loki/ScopeGuard.h" #include <boost/scoped_array.hpp> #include "system_func.h" -#include <wx/intl.h> #include "string_conv.h" #include "file_error.h" +#include "i18n.h" #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" @@ -72,7 +72,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw (FileError) try //reading certain symlinks requires admin rights! This shall not cause an error in user mode! { //allow access to certain symbolic links/junctions - ffs3::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError() + ffs3::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw (FileError) } catch (...) {} diff --git a/shared/system_constants.h b/shared/system_constants.h index 6f89609e..9c0cd4a4 100644 --- a/shared/system_constants.h +++ b/shared/system_constants.h @@ -16,7 +16,7 @@ namespace common // GLOBALS //------------------------------------------------ #ifdef FFS_WIN -const Zchar FILE_NAME_SEPARATOR = '\\'; +const Zchar FILE_NAME_SEPARATOR = '\\'; // const wxChar LINE_BREAK[] = wxT("\r\n"); //internal linkage #elif defined FFS_LINUX const Zchar FILE_NAME_SEPARATOR = '/'; @@ -26,5 +26,4 @@ const wxChar LINE_BREAK[] = wxT("\n"); const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF"; } - #endif // SYSTEMCONSTANTS_H_INCLUDED diff --git a/shared/util.cpp b/shared/util.cpp index 2af7dc5b..7ab5b4e7 100644 --- a/shared/util.cpp +++ b/shared/util.cpp @@ -9,7 +9,7 @@ #include <wx/textctrl.h> #include <wx/combobox.h> #include <wx/filepicker.h> -#include "localization.h" +#include "i18n.h" #include "file_handling.h" #include "string_conv.h" #include <stdexcept> @@ -157,7 +157,7 @@ wxString ffs3::utcTimeToLocalString(const wxLongLong& utcTime) fileTimeLong += wxLongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s fileTimeLong *= 10000000; - FILETIME lastWriteTimeUtc; + FILETIME lastWriteTimeUtc = {}; lastWriteTimeUtc.dwLowDateTime = fileTimeLong.GetLo(); //GetLo() returns unsigned lastWriteTimeUtc.dwHighDateTime = static_cast<DWORD>(fileTimeLong.GetHi()); //GetHi() returns signed @@ -212,25 +212,6 @@ wxString ffs3::utcTimeToLocalString(const wxLongLong& utcTime) wxT("\n\n") + getLastErrorFormatted()).ToAscii())); } - /* - //assemble time string (performance optimized) -> note: performance argument is not valid anymore - - wxString formattedTime; - formattedTime.reserve(20); - - writeFourDigitNumber(time.wYear, formattedTime); - formattedTime += wxChar('-'); - writeTwoDigitNumber(time.wMonth, formattedTime); - formattedTime += wxChar('-'); - writeTwoDigitNumber(time.wDay, formattedTime); - formattedTime += wxChar(' '); - formattedTime += wxChar(' '); - writeTwoDigitNumber(time.wHour, formattedTime); - formattedTime += wxChar(':'); - writeTwoDigitNumber(time.wMinute, formattedTime); - formattedTime += wxChar(':'); - writeTwoDigitNumber(time.wSecond, formattedTime); - */ const wxDateTime localTime(systemTimeLocal.wDay, wxDateTime::Month(systemTimeLocal.wMonth - 1), systemTimeLocal.wYear, diff --git a/shared/xml_base.cpp b/shared/xml_base.cpp index c3d04364..3f2a9e48 100644 --- a/shared/xml_base.cpp +++ b/shared/xml_base.cpp @@ -5,8 +5,8 @@ // ************************************************************************** // #include "xml_base.h" -#include <wx/intl.h> #include "file_io.h" +#include "i18n.h" #include "string_conv.h" #include "system_constants.h" #include <boost/scoped_array.hpp> @@ -68,8 +68,8 @@ void loadRawXmlDocument(const wxString& filename, TiXmlDocument& document) //thr try { - FileInput inputFile(wxToZ(filename)); //throw FileError(); - const size_t bytesRead = inputFile.read(&inputBuffer[0], inputBuffer.size()); //throw FileError() + FileInput inputFile(wxToZ(filename)); //throw (FileError); + const size_t bytesRead = inputFile.read(&inputBuffer[0], inputBuffer.size()); //throw (FileError) if (bytesRead == 0 || bytesRead >= inputBuffer.size()) //treat XML files larger than 2 MB as erroneous: loading larger files just wastes CPU + memory throw XmlError(wxString(_("Error parsing configuration file:")) + wxT("\n\"") + filename + wxT("\"")); @@ -165,14 +165,14 @@ bool saveNecessary(const Zstring& filename, const std::string& dataToWrite) //th { try { - if (ffs3::getFilesize(filename) != static_cast<unsigned long>(dataToWrite.size())) //throw FileError(); + if (ffs3::getFilesize(filename) != static_cast<unsigned long>(dataToWrite.size())) //throw (FileError); return true; boost::scoped_array<char> inputBuffer(new char[dataToWrite.size() + 1]); //+ 1 in order to test for end of file! - FileInput inputFile(filename); //throw FileError(); + FileInput inputFile(filename); //throw (FileError); - const size_t bytesRead = inputFile.read(inputBuffer.get(), dataToWrite.size() + 1); //throw FileError() + const size_t bytesRead = inputFile.read(inputBuffer.get(), dataToWrite.size() + 1); //throw (FileError) if (bytesRead != dataToWrite.size()) //implicit test for eof! return true; @@ -200,7 +200,7 @@ void xmlAccess::saveXmlDocument(const wxString& filename, const TiXmlDocument& d { try { - FileOutput outputFile(wxToZ(filename)); //throw FileError() + FileOutput outputFile(wxToZ(filename), FileOutput::ACC_OVERWRITE); //throw (FileError) outputFile.write(buffer.c_str(), buffer.length()); // } catch (const FileError& error) //more detailed error messages than with wxWidgets diff --git a/shared/zbase.h b/shared/zbase.h index a0322de0..f08a87e3 100644 --- a/shared/zbase.h +++ b/shared/zbase.h @@ -125,7 +125,7 @@ private: static size_t calcCapacity(size_t length) { - return (length + (19 - length % 16)); //allocate some additional length to speed up concatenation + return std::max<size_t>(16, length + length / 2); //exponential growth + min size } }; @@ -207,7 +207,7 @@ private: static size_t calcCapacity(size_t length) { - return (length + (19 - length % 16)); //allocate some additional length to speed up concatenation + return std::max<size_t>(16, length + length / 2); //exponential growth + min size } }; @@ -228,6 +228,11 @@ public: operator const T* () const; //implicit conversion to C-string //STL accessors + typedef T* iterator; + typedef const T* const_iterator; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; const T* begin() const; const T* end() const; T* begin(); @@ -262,11 +267,13 @@ public: size_t find(const T* str, size_t pos = 0) const; //returns "npos" if not found size_t find(T ch, size_t pos = 0) const; // size_t rfind(T ch, size_t pos = npos) const; // + size_t rfind(const T* str, size_t pos = npos) const; // Zbase& replace(size_t pos1, size_t n1, const T* str, size_t n2); void reserve(size_t minCapacity); Zbase& assign(const T* source, size_t len); void resize(size_t newSize, T fillChar = 0); void swap(Zbase& other); + void push_back(T val); //STL access //number conversion template <class N> static Zbase fromNumber(N number); @@ -620,6 +627,21 @@ size_t Zbase<T, SP, AP>::rfind(T ch, size_t pos) const template <class T, template <class, class> class SP, class AP> +inline +size_t Zbase<T, SP, AP>::rfind(const T* str, size_t pos) const +{ + assert(pos == npos || pos <= length()); + + const size_t strLen = z_impl::cStringLength(str); + const T* currEnd = pos == npos ? end() : begin() + std::min(pos + strLen, length()); + + const T* iter = std::find_end(begin(), currEnd, + str, str + strLen); + return iter == currEnd ? npos : iter - begin(); +} + + +template <class T, template <class, class> class SP, class AP> Zbase<T, SP, AP>& Zbase<T, SP, AP>::replace(size_t pos1, size_t n1, const T* str, size_t n2) { assert(str < rawStr || rawStr + length() < str); //str mustn't point to data in this string @@ -863,6 +885,14 @@ T* Zbase<T, SP, AP>::end() template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::push_back(T val) +{ + operator+=(val); +} + + +template <class T, template <class, class> class SP, class AP> +inline bool Zbase<T, SP, AP>::empty() const { return length() == 0; @@ -1067,8 +1097,8 @@ template <class N> inline N Zbase<T, SP, AP>::toNumber() const { - std::basic_istringstream<T> ss(std::basic_string<T>(rawStr)); - T number = 0; + std::basic_istringstream<T> ss((std::basic_string<T>(rawStr))); + N number = 0; ss >> number; return number; } diff --git a/shared/zstring.h b/shared/zstring.h index 73e2f066..286ed7cc 100644 --- a/shared/zstring.h +++ b/shared/zstring.h @@ -115,10 +115,10 @@ typedef char Zchar; #endif //"The reason for all the fuss above" (Loki/SmartPtr) -typedef Zbase<Zchar, StorageRefCount, AllocatorFreeStoreChecked> Zstring; - - +typedef Zbase<Zchar, StorageRefCount, AllocatorFreeStoreChecked> Zstring; //for use with file names +//fast replacement for wxString modelling exponential growth +typedef Zbase<wchar_t, StorageRefCount, AllocatorFreeStoreChecked> zxString; //general unicode string |