From 110fc5dee14fc7988f631a158e50d283446aba7a Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:24:09 +0200 Subject: 5.15 --- lib/db_file.cpp | 13 +- lib/dir_lock.cpp | 8 +- lib/generate_logfile.h | 3 +- lib/icon_buffer.cpp | 5 +- lib/lock_holder.h | 28 ++-- lib/osx_file_icon.h | 2 +- lib/osx_file_icon.mm | 14 +- lib/parallel_scan.h | 5 +- lib/process_xml.cpp | 400 +++++++++++++++++++++++++++++++------------------ lib/process_xml.h | 27 +++- lib/shadow.cpp | 3 +- lib/shadow.h | 3 +- lib/soft_filter.h | 4 +- lib/xml_base.cpp | 34 ++--- lib/xml_base.h | 2 +- 15 files changed, 337 insertions(+), 214 deletions(-) (limited to 'lib') diff --git a/lib/db_file.cpp b/lib/db_file.cpp index 368cf56b..aa893711 100644 --- a/lib/db_file.cpp +++ b/lib/db_file.cpp @@ -24,7 +24,7 @@ namespace { //------------------------------------------------------------------------------------------------------------------------------- const char FILE_FORMAT_DESCR[] = "FreeFileSync"; -const int FILE_FORMAT_VER = 9; +const int DB_FILE_FORMAT_VER = 9; //------------------------------------------------------------------------------------------------------------------------------- typedef std::string UniqueId; @@ -39,6 +39,7 @@ template inline Zstring getDBFilename(const BaseDirMapping& baseMap, bool tempfile = false) { //Linux and Windows builds are binary incompatible: different file id?, problem with case sensitivity? + //what about endianess!? //however 32 and 64 bit db files *are* designed to be binary compatible! //Give db files different names. //make sure they end with ".ffs_db". These files will be excluded from comparison @@ -63,7 +64,7 @@ void saveStreams(const StreamMapping& streamList, const Zstring& filename) //thr writeArray(streamOut, FILE_FORMAT_DESCR, sizeof(FILE_FORMAT_DESCR)); //save file format version - writeNumber(streamOut, FILE_FORMAT_VER); + writeNumber(streamOut, DB_FILE_FORMAT_VER); //save stream list writeNumber(streamOut, static_cast(streamList.size())); //number of streams, one for each sync-pair @@ -96,7 +97,7 @@ StreamMapping loadStreams(const Zstring& filename) //throw FileError, FileErrorD throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); const int version = readNumber(streamIn); //throw UnexpectedEndOfStreamError - if (version != FILE_FORMAT_VER) //read file format version# + if (version != DB_FILE_FORMAT_VER) //read file format version# throw FileError(replaceCpy(_("Database file %x is incompatible."), L"%x", fmtFileName(filename))); //read stream lists @@ -363,7 +364,7 @@ private: void recurse(InSyncDir& container) { size_t fileCount = readNumber(inputBoth); - while (fileCount-- != 0) //files + while (fileCount-- != 0) { const Zstring shortName = readUtf8(inputBoth); const auto inSyncType = static_cast(readNumber(inputBoth)); @@ -377,7 +378,7 @@ private: } size_t linkCount = readNumber(inputBoth); - while (linkCount-- != 0) //files + while (linkCount-- != 0) { const Zstring shortName = readUtf8(inputBoth); @@ -390,7 +391,7 @@ private: } size_t dirCount = readNumber(inputBoth); - while (dirCount-- != 0) //files + while (dirCount-- != 0) { const Zstring shortName = readUtf8(inputBoth); diff --git a/lib/dir_lock.cpp b/lib/dir_lock.cpp index f263d4ac..328b87d3 100644 --- a/lib/dir_lock.cpp +++ b/lib/dir_lock.cpp @@ -98,7 +98,7 @@ public: FILE_END)) //__in DWORD dwMoveMethod return; - DWORD bytesWritten = 0; + DWORD bytesWritten = 0; //this parameter is NOT optional: http://blogs.msdn.com/b/oldnewthing/archive/2013/04/04/10407417.aspx /*bool rv = */ ::WriteFile(fileHandle, //__in HANDLE hFile, buffer, //__out LPVOID lpBuffer, @@ -375,7 +375,7 @@ LockInformation retrieveLockInfo(const Zstring& lockfilename) //throw FileError, } catch (UnexpectedEndOfStreamError&) { - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(lockfilename))); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(lockfilename)) + L" (unexpected end of stream)"); } } @@ -535,7 +535,7 @@ bool tryLock(const Zstring& lockfilename) //throw FileError lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 return false; else - throw FileError(replaceCpy(_("Cannot set directory lock %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + zen::getLastErrorFormatted()); } ::CloseHandle(fileHandle); @@ -550,7 +550,7 @@ bool tryLock(const Zstring& lockfilename) //throw FileError if (errno == EEXIST) return false; else - throw FileError(replaceCpy(_("Cannot set directory lock %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(lockfilename)) + L"\n\n" + zen::getLastErrorFormatted()); } ::close(fileHandle); #endif diff --git a/lib/generate_logfile.h b/lib/generate_logfile.h index 875d5b98..c441de66 100644 --- a/lib/generate_logfile.h +++ b/lib/generate_logfile.h @@ -119,7 +119,6 @@ inline void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError const ErrorLog& log, size_t maxBytesToWrite) //log may be *huge*, e.g. 1 million items; LastSyncs.log *must not* create performance problems! - { const Zstring filename = getConfigDir() + Zstr("LastSyncs.log"); @@ -127,7 +126,7 @@ void saveToLastSyncsLog(const SummaryInfo& summary, //throw FileError replace(newStream, '\n', LINE_BREAK); //don't replace line break any earlier newStream += LINE_BREAK; - //write log items one after the other instead of creating one big string: memory allocation might fail; think 1 million entries! + //check size of "newStream": memory allocation might fail - think 1 million entries! for (auto iter = log.begin(); iter != log.end(); ++iter) { newStream += replaceCpy(utfCvrtTo(formatMessage(*iter)), '\n', LINE_BREAK); diff --git a/lib/icon_buffer.cpp b/lib/icon_buffer.cpp index dd80f6dd..3912849e 100644 --- a/lib/icon_buffer.cpp +++ b/lib/icon_buffer.cpp @@ -359,11 +359,12 @@ IconHolder getAssociatedIcon(const Zstring& filename, IconBuffer::IconSize sz) sizeof(fileInfo), //UINT cbFileInfo, SHGFI_SYSICONINDEX | SHGFI_ATTRIBUTES)) //UINT uFlags { + (void)imgList; //imgList->Release(); //empiric study: crash on XP if we release this! Seems we do not own it... -> also no GDI leak on Win7 -> okay //another comment on http://msdn.microsoft.com/en-us/library/bb762179(v=VS.85).aspx describes exact same behavior on Win7/XP - //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as - // needed; for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList." + //Quote: "The IImageList pointer type, such as that returned in the ppv parameter, can be cast as an HIMAGELIST as needed; + // for example, for use in a list view. Conversely, an HIMAGELIST can be cast as a pointer to an IImageList." //http://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx #ifndef SFGAO_LINK //Shobjidl.h diff --git a/lib/lock_holder.h b/lib/lock_holder.h index 9cde59a7..81b5632d 100644 --- a/lib/lock_holder.h +++ b/lib/lock_holder.h @@ -1,31 +1,29 @@ -#ifndef LOCK_HOLDER_H_INCLUDED -#define LOCK_HOLDER_H_INCLUDED +#ifndef LOCK_HOLDER_H_489572039485723453425 +#define LOCK_HOLDER_H_489572039485723453425 -#include +#include #include #include #include "dir_lock.h" #include "status_handler.h" -#include "dir_exist_async.h" +//#include "dir_exist_async.h" namespace zen { const Zstring LOCK_FILE_ENDING = Zstr(".ffs_lock"); //intermediate locks created by DirLock use this extension, too! //hold locks for a number of directories without blocking during lock creation +//call after having checked directory existence! class LockHolder { public: - LockHolder(const std::vector& dirnamesFmt, //resolved dirname ending with path separator - ProcessCallback& procCallback, - bool allowUserInteraction) + LockHolder(const std::set& dirnamesExisting, //resolved dirname ending with path separator + bool& warningDirectoryLockFailed, + ProcessCallback& procCallback) { - std::set existingDirs = getExistingDirsUpdating(dirnamesFmt, allowUserInteraction, procCallback); - - for (auto it = existingDirs.begin(); it != existingDirs.end(); ++it) + for (auto it = dirnamesExisting.begin(); it != dirnamesExisting.end(); ++it) { const Zstring& dirnameFmt = *it; - assert(endsWith(dirnameFmt, FILE_NAME_SEPARATOR)); //this is really the contract, formatting does other things as well, e.g. macro substitution class WaitOnLockHandler : public DirLockCallback @@ -45,8 +43,8 @@ public: } catch (const FileError& e) { - bool dummy = false; //this warning shall not be shown but logged only - procCallback.reportWarning(e.toString(), dummy); //may throw! + const std::wstring msg = replaceCpy(_("Cannot set directory lock for %x."), L"%x", fmtFileName(dirnameFmt)) + L"\n\n" + e.toString(); + procCallback.reportWarning(msg, warningDirectoryLockFailed); //may throw! } } } @@ -54,8 +52,6 @@ public: private: std::vector lockHolder; }; - } - -#endif // LOCK_HOLDER_H_INCLUDED +#endif //LOCK_HOLDER_H_489572039485723453425 diff --git a/lib/osx_file_icon.h b/lib/osx_file_icon.h index 1d57a00e..e9b17988 100644 --- a/lib/osx_file_icon.h +++ b/lib/osx_file_icon.h @@ -14,7 +14,7 @@ namespace osx { struct ImageData { - ImageData(int w, int h) : width(w), height(h), rgb(w * h * 3), alpha(w * h) {} + ImageData(int w, int h) : width(w), height(h), rgb(w* h * 3), alpha(w* h) {} ImageData(ImageData&& tmp) : width(tmp.width), height(tmp.height) { rgb.swap(tmp.rgb); alpha.swap(tmp.alpha); } const int width; diff --git a/lib/osx_file_icon.mm b/lib/osx_file_icon.mm index 6a068998..11fb053f 100644 --- a/lib/osx_file_icon.mm +++ b/lib/osx_file_icon.mm @@ -31,8 +31,8 @@ osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw OsxError; CGImageRef imgRef = [nsImg CGImageForProposedRect:&rectProposed context:nil hints:nil]; ZEN_OSX_ASSERT(imgRef != NULL); //can this fail? not documented; ownership?? not documented! - const size_t width = CGImageGetWidth (imgRef); - const size_t height = CGImageGetHeight(imgRef); + const size_t width = ::CGImageGetWidth (imgRef); + const size_t height = ::CGImageGetHeight(imgRef); ZEN_OSX_ASSERT(width > 0 && height > 0 && requestedSize > 0); @@ -46,14 +46,14 @@ osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw OsxError; trgHeight = height * requestedSize / maxExtent; } - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGColorSpaceRef colorSpace = ::CGColorSpaceCreateDeviceRGB(); ZEN_OSX_ASSERT(colorSpace != NULL); //may fail - ZEN_ON_SCOPE_EXIT(CGColorSpaceRelease(colorSpace)); + ZEN_ON_SCOPE_EXIT(::CGColorSpaceRelease(colorSpace)); std::vector buf(trgWidth* trgHeight * 4); //32-bit ARGB, little endian byte order -> already initialized with 0 = fully transparent //supported color spaces: https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_context/dq_context.html#//apple_ref/doc/uid/TP30001066-CH203-BCIBHHBB - CGContextRef ctxRef = CGBitmapContextCreate(&buf[0], //void *data, + CGContextRef ctxRef = ::CGBitmapContextCreate(&buf[0], //void *data, trgWidth, //size_t width, trgHeight, //size_t height, 8, //size_t bitsPerComponent, @@ -61,9 +61,9 @@ osx::ImageData extractBytes(NSImage* nsImg, int requestedSize) //throw OsxError; colorSpace, //CGColorSpaceRef colorspace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); //CGBitmapInfo bitmapInfo ZEN_OSX_ASSERT(ctxRef != NULL); - ZEN_ON_SCOPE_EXIT(CGContextRelease(ctxRef)); + ZEN_ON_SCOPE_EXIT(::CGContextRelease(ctxRef)); - CGContextDrawImage(ctxRef, CGRectMake(0, 0, trgWidth, trgHeight), imgRef); //can this fail? not documented + ::CGContextDrawImage(ctxRef, CGRectMake(0, 0, trgWidth, trgHeight), imgRef); //can this fail? not documented //CGContextFlush(ctxRef); //"If you pass [...] a bitmap context, this function does nothing." diff --git a/lib/parallel_scan.h b/lib/parallel_scan.h index f04d51b4..5a52e44e 100644 --- a/lib/parallel_scan.h +++ b/lib/parallel_scan.h @@ -35,8 +35,9 @@ bool operator<(const DirectoryKey& lhs, const DirectoryKey& rhs) if (lhs.handleSymlinks_ != rhs.handleSymlinks_) return lhs.handleSymlinks_ < rhs.handleSymlinks_; - if (!EqualFilename()(lhs.dirnameFull_, rhs.dirnameFull_)) - return LessFilename()(lhs.dirnameFull_, rhs.dirnameFull_); + const int cmpName = cmpFileName(lhs.dirnameFull_, rhs.dirnameFull_); + if (cmpName != 0) + return cmpName < 0; return *lhs.filter_ < *rhs.filter_; } diff --git a/lib/process_xml.cpp b/lib/process_xml.cpp index d41afc74..4974dcbc 100644 --- a/lib/process_xml.cpp +++ b/lib/process_xml.cpp @@ -1,4 +1,4 @@ -// ************************************************************************** +// ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * @@ -17,6 +17,14 @@ using namespace xmlAccess; //functionally needed for correct overload resolution using namespace std::rel_ops; +namespace +{ +//------------------------------------------------------------------------------------------------------------------------------- +const int XML_FORMAT_VER_GLOBAL = 1; +const int XML_FORMAT_VER_FFS_GUI = 1; +const int XML_FORMAT_VER_FFS_BATCH = 1; +//------------------------------------------------------------------------------------------------------------------------------- +} XmlType getXmlType(const zen::XmlDoc& doc) //throw() { @@ -73,9 +81,9 @@ void setXmlType(XmlDoc& doc, XmlType type) //throw() } //################################################################################################################ -wxString xmlAccess::getGlobalConfigFile() +Zstring xmlAccess::getGlobalConfigFile() { - return utfCvrtTo(zen::getConfigDir()) + L"GlobalSettings.xml"; + return zen::getConfigDir() + Zstr("GlobalSettings.xml"); } @@ -88,6 +96,8 @@ void xmlAccess::OptionalDialogs::resetDialogs() warningUnresolvedConflicts = true; warningDatabaseError = true; warningRecyclerMissing = true; + warningInputFieldEmpty = true; + warningDirectoryLockFailed = true; popupOnConfigChange = true; confirmSyncStart = true; } @@ -180,67 +190,21 @@ xmlAccess::MergeType xmlAccess::getMergeType(const std::vector& filenam namespace { -template -XmlCfg readConfigNoWarnings(const Zstring& filename, std::unique_ptr& warning) //throw FfsXmlError, but only if "FATAL" +std::vector splitFilterByLines(const Zstring& filterPhrase) { - XmlCfg cfg; - try - { - readConfig(filename, cfg); //throw xmlAccess::FfsXmlError - } - catch (const FfsXmlError& e) - { - if (e.getSeverity() == FfsXmlError::FATAL) - throw; - else if (!warning.get()) warning = make_unique(e); - } - return cfg; + if (filterPhrase.empty()) + return std::vector(); + return split(filterPhrase, Zstr('\n')); } -} - -void xmlAccess::readAnyConfig(const std::vector& filenames, XmlGuiConfig& config) //throw FfsXmlError +Zstring mergeFilterLines(const std::vector& filterLines) { - assert(!filenames.empty()); - - std::vector mainCfgs; - std::unique_ptr savedWarning; - - for (auto it = filenames.begin(); it != filenames.end(); ++it) - { - const Zstring& filename = *it; - const bool firstLine = it == filenames.begin(); //init all non-"mainCfg" settings with first config file - - switch (getXmlType(filename)) - { - case XML_TYPE_GUI: - if (firstLine) - config = readConfigNoWarnings(filename, savedWarning); - else - mainCfgs.push_back(readConfigNoWarnings(filename, savedWarning).mainCfg); //throw FfsXmlError - break; - - case XML_TYPE_BATCH: - if (firstLine) - config = convertBatchToGui(readConfigNoWarnings(filename, savedWarning)); - else - mainCfgs.push_back(readConfigNoWarnings(filename, savedWarning).mainCfg); //throw FfsXmlError - break; - - case XML_TYPE_GLOBAL: - case XML_TYPE_OTHER: - if (!fileExists(filename)) - throw FfsXmlError(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename))); - else - throw FfsXmlError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename))); - } - } - mainCfgs.push_back(config.mainCfg); //save cfg from first line - - config.mainCfg = merge(mainCfgs); - - if (savedWarning.get()) //"re-throw" exception - throw* savedWarning; + if (filterLines.empty()) + return Zstring(); + Zstring out = filterLines[0]; + std::for_each(filterLines.begin() + 1, filterLines.end(), [&](const Zstring& line) { out += Zstr('\n'); out += line; }); + return out; +} } @@ -594,6 +558,9 @@ void writeText(const ColumnTypeNavi& value, std::string& output) case COL_TYPE_NAVI_DIRECTORY: output = "Tree"; break; + case COL_TYPE_NAVI_ITEM_COUNT: + output = "Count"; + break; } } @@ -606,6 +573,8 @@ bool readText(const std::string& input, ColumnTypeNavi& value) value = COL_TYPE_NAVI_BYTES; else if (tmp == "Tree") value = COL_TYPE_NAVI_DIRECTORY; + else if (tmp == "Count") + value = COL_TYPE_NAVI_ITEM_COUNT; else return false; return true; @@ -870,8 +839,33 @@ void readConfig(const XmlIn& in, SyncConfig& syncCfg) void readConfig(const XmlIn& in, FilterConfig& filter) { - in["Include"](filter.includeFilter); - in["Exclude"](filter.excludeFilter); + warn_static("remove after migration?") + auto haveFilterAsSingleString = [&]() -> bool + { + if (in["Include"]) + if (auto elem = in["Include"].get()) + { + std::string tmp; + if (elem->getValue(tmp)) + return !tmp.empty(); + } + return false; + }; + if (haveFilterAsSingleString()) //obsolete style + { + in["Include"](filter.includeFilter); + in["Exclude"](filter.excludeFilter); + } + else + { + std::vector tmp = splitFilterByLines(filter.includeFilter); //default value + in["Include"](tmp); + filter.includeFilter = mergeFilterLines(tmp); + + std::vector tmp2 = splitFilterByLines(filter.excludeFilter); //default value + in["Exclude"](tmp2); + filter.excludeFilter = mergeFilterLines(tmp2); + } in["TimeSpan"](filter.timeSpan); warn_static("remove after migration?") @@ -942,15 +936,15 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg) //read all folder pairs mainCfg.additionalPairs.clear(); - bool firstIter = true; + bool firstItem = true; for (XmlIn inPair = inMain["FolderPairs"]["Pair"]; inPair; inPair.next()) { FolderPairEnh newPair; readConfig(inPair, newPair); - if (firstIter) + if (firstItem) { - firstIter = false; + firstItem = false; mainCfg.firstPair = newPair; //set first folder pair } else @@ -966,7 +960,7 @@ void readConfig(const XmlIn& in, MainConfiguration& mainCfg) void readConfig(const XmlIn& in, xmlAccess::XmlGuiConfig& config) { - ::readConfig(in, config.mainCfg); //read main config + readConfig(in, config.mainCfg); //read main config //read GUI specific config data XmlIn inGuiCfg = in["GuiConfig"]; @@ -998,7 +992,7 @@ void readConfig(const XmlIn& in, xmlAccess::XmlGuiConfig& config) void readConfig(const XmlIn& in, xmlAccess::XmlBatchConfig& config) { - ::readConfig(in, config.mainCfg); //read main config + readConfig(in, config.mainCfg); //read main config //read GUI specific config data XmlIn inBatchCfg = in["BatchConfig"]; @@ -1021,31 +1015,29 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) { XmlIn inShared = in["Shared"]; - //try to read program language setting - inShared["Language"](config.programLanguage); - - inShared["CopyLockedFiles" ](config.copyLockedFiles); - inShared["CopyFilePermissions" ](config.copyFilePermissions); - inShared["TransactionalFileCopy"](config.transactionalFileCopy); - inShared["LockDirectoriesDuringSync"](config.createLockFile); - inShared["VerifyCopiedFiles" ](config.verifyFileCopy); - inShared["RunWithBackgroundPriority"](config.runWithBackgroundPriority); + inShared["Language"].attribute("id", config.programLanguage); - //max. allowed file time deviation - inShared["FileTimeTolerance"](config.fileTimeTolerance); - - inShared["LastSyncsFileSizeMax"](config.lastSyncsLogFileSizeMax); + inShared["FailSafeFileCopy" ].attribute("Enabled", config.transactionalFileCopy); + inShared["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); + inShared["CopyFilePermissions" ].attribute("Enabled", config.copyFilePermissions); + inShared["RunWithBackgroundPriority"].attribute("Enabled", config.runWithBackgroundPriority); + inShared["LockDirectoriesDuringSync"].attribute("Enabled", config.createLockFile); + inShared["VerifyCopiedFiles" ].attribute("Enabled", config.verifyFileCopy); + inShared["FileTimeTolerance" ].attribute("Seconds", config.fileTimeTolerance); + inShared["LastSyncsLogSizeMax" ].attribute("Bytes" , config.lastSyncsLogFileSizeMax); XmlIn inOpt = inShared["OptionalDialogs"]; - inOpt["WarnUnresolvedConflicts" ](config.optDialogs.warningUnresolvedConflicts); - inOpt["WarnNotEnoughDiskSpace" ](config.optDialogs.warningNotEnoughDiskSpace); - inOpt["WarnSignificantDifference" ](config.optDialogs.warningSignificantDifference); - inOpt["WarnRecycleBinNotAvailable" ](config.optDialogs.warningRecyclerMissing); - inOpt["WarnDatabaseError" ](config.optDialogs.warningDatabaseError); - inOpt["WarnDependentFolders" ](config.optDialogs.warningDependentFolders); - inOpt["WarnFolderPairRaceCondition"](config.optDialogs.warningFolderPairRaceCondition); - inOpt["PromptSaveConfig" ](config.optDialogs.popupOnConfigChange); - inOpt["ConfirmSyncStart" ](config.optDialogs.confirmSyncStart); + inOpt["WarnUnresolvedConflicts" ].attribute("Enabled", config.optDialogs.warningUnresolvedConflicts); + inOpt["WarnNotEnoughDiskSpace" ].attribute("Enabled", config.optDialogs.warningNotEnoughDiskSpace); + inOpt["WarnSignificantDifference" ].attribute("Enabled", config.optDialogs.warningSignificantDifference); + inOpt["WarnRecycleBinNotAvailable" ].attribute("Enabled", config.optDialogs.warningRecyclerMissing); + inOpt["WarnInputFieldEmpty" ].attribute("Enabled", config.optDialogs.warningInputFieldEmpty); + inOpt["WarnDatabaseError" ].attribute("Enabled", config.optDialogs.warningDatabaseError); + inOpt["WarnDependentFolders" ].attribute("Enabled", config.optDialogs.warningDependentFolders); + inOpt["WarnFolderPairRaceCondition"].attribute("Enabled", config.optDialogs.warningFolderPairRaceCondition); + inOpt["WarnDirectoryLockFailed" ].attribute("Enabled", config.optDialogs.warningDirectoryLockFailed); + inOpt["ConfirmSaveConfig" ].attribute("Enabled", config.optDialogs.popupOnConfigChange); + inOpt["ConfirmStartSync" ].attribute("Enabled", config.optDialogs.confirmSyncStart); //gui specific global settings (optional) XmlIn inGui = in["Gui"]; @@ -1062,24 +1054,25 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) //inManualDel.attribute("DeleteOnBothSides", config.gui.deleteOnBothSides); inManualDel.attribute("UseRecycler" , config.gui.useRecyclerForManualDeletion); - inWnd["CaseSensitiveSearch" ](config.gui.textSearchRespectCase); - inWnd["MaxFolderPairsVisible"](config.gui.maxFolderPairsVisible); + inWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.textSearchRespectCase); + inWnd["FolderPairsVisible" ].attribute("Max", config.gui.maxFolderPairsVisible); //########################################################### + + XmlIn inOverview = inWnd["OverviewPanel"]; + inOverview.attribute("ShowPercentage", config.gui.showPercentBar); + inOverview.attribute("SortByColumn", config.gui.naviLastSortColumn); + inOverview.attribute("SortAscending", config.gui.naviLastSortAscending); + //read column attributes - XmlIn inColNavi = inWnd["OverviewColumns"]; + XmlIn inColNavi = inOverview["Columns"]; inColNavi(config.gui.columnAttribNavi); - inColNavi.attribute("ShowPercentage", config.gui.showPercentBar); - inColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); - inColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); - XmlIn inMainGrid = inWnd["MainGrid"]; inMainGrid.attribute("ShowIcons", config.gui.showIcons); inMainGrid.attribute("IconSize", config.gui.iconSize); inMainGrid.attribute("SashOffset", config.gui.sashOffset); - XmlIn inColLeft = inMainGrid["ColumnsLeft"]; inColLeft(config.gui.columnAttribLeft); @@ -1088,11 +1081,14 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) //########################################################### inWnd["ViewFilterDefault"](config.gui.viewFilterDefault); - inWnd["Layout" ](config.gui.guiPerspectiveLast); + inWnd["Perspective" ](config.gui.guiPerspectiveLast); - //load config file history - inGui["LastUsedConfig"](config.gui.lastUsedConfigFiles); + std::vector tmp = splitFilterByLines(config.gui.defaultExclusionFilter); //default value + inGui["DefaultExclusionFilter"](tmp); + config.gui.defaultExclusionFilter = mergeFilterLines(tmp); + //load config file history + inGui["LastUsedConfig"](config.gui.lastUsedConfigFiles); inGui["ConfigHistory"](config.gui.cfgFileHistory); inGui["ConfigHistory"].attribute("MaxSize", config.gui.cfgFileHistMax); @@ -1114,44 +1110,146 @@ void readConfig(const XmlIn& in, XmlGlobalSettings& config) } +bool needsMigration(const XmlDoc& doc, int currentXmlFormatVer) +{ + //(try to) migrate old configuration if needed + int xmlFormatVer = 0; + /*bool success = */doc.root().getAttribute("XmlFormat", xmlFormatVer); + return xmlFormatVer < currentXmlFormatVer; +} + + template -void readConfig(const Zstring& filename, XmlType type, ConfigType& config) +void readConfig(const Zstring& filename, XmlType type, ConfigType& cfg, int currentXmlFormatVer, bool& needMigration) //throw FfsXmlError { XmlDoc doc; loadXmlDocument(filename, doc); //throw FfsXmlError - + if (getXmlType(doc) != type) //throw() throw FfsXmlError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename))); XmlIn in(doc); - ::readConfig(in, config); + ::readConfig(in, cfg); if (in.errorsOccured()) throw FfsXmlError(replaceCpy(_("Configuration file %x loaded partially only."), L"%x", fmtFileName(filename)) + L"\n\n" + - getErrorMessageFormatted(in), FfsXmlError::WARNING); + getErrorMessageFormatted(in.getErrorsAs()), FfsXmlError::WARNING); + + //(try to) migrate old configuration if needed + needMigration = needsMigration(doc, currentXmlFormatVer); } } -void xmlAccess::readConfig(const Zstring& filename, xmlAccess::XmlGuiConfig& config) +void xmlAccess::readConfig(const Zstring& filename, xmlAccess::XmlGuiConfig& cfg) +{ + bool needMigration = false; + ::readConfig(filename, XML_TYPE_GUI, cfg, XML_FORMAT_VER_FFS_GUI, needMigration); //throw FfsXmlError + + if (needMigration) //(try to) migrate old configuration + try { xmlAccess::writeConfig(cfg, filename); /*throw FfsXmlError*/ } + catch (FfsXmlError&) { assert(false); } //don't bother user! +} + + +void xmlAccess::readConfig(const Zstring& filename, xmlAccess::XmlBatchConfig& cfg) { - ::readConfig(filename, XML_TYPE_GUI, config); + bool needMigration = false; + ::readConfig(filename, XML_TYPE_BATCH, cfg, XML_FORMAT_VER_FFS_BATCH, needMigration); //throw FfsXmlError + + if (needMigration) //(try to) migrate old configuration + try { xmlAccess::writeConfig(cfg, filename); /*throw FfsXmlError*/ } + catch (FfsXmlError&) { assert(false); } //don't bother user! } -void xmlAccess::readConfig(const Zstring& filename, xmlAccess::XmlBatchConfig& config) +void xmlAccess::readConfig(xmlAccess::XmlGlobalSettings& cfg) { - ::readConfig(filename, XML_TYPE_BATCH, config); + bool needMigration = false; + ::readConfig(getGlobalConfigFile(), XML_TYPE_GLOBAL, cfg, XML_FORMAT_VER_GLOBAL, needMigration); //throw FfsXmlError } -void xmlAccess::readConfig(xmlAccess::XmlGlobalSettings& config) +namespace +{ +template +XmlCfg parseConfig(const XmlDoc& doc, const Zstring& filename, int currentXmlFormatVer, std::unique_ptr& warning) //nothrow { - ::readConfig(utfCvrtTo(getGlobalConfigFile()), XML_TYPE_GLOBAL, config); + XmlCfg cfg; + XmlIn in(doc); + ::readConfig(in, cfg); + + if (in.errorsOccured()) + { + if (!warning) + warning = make_unique(replaceCpy(_("Configuration file %x loaded partially only."), L"%x", fmtFileName(filename)) + L"\n\n" + + getErrorMessageFormatted(in.getErrorsAs()), FfsXmlError::WARNING); + } + else + { + //(try to) migrate old configuration if needed + if (needsMigration(doc, currentXmlFormatVer)) + try { xmlAccess::writeConfig(cfg, filename); /*throw FfsXmlError*/ } + catch (FfsXmlError&) { assert(false); } //don't bother user! + } + return cfg; +} } +void xmlAccess::readAnyConfig(const std::vector& filenames, XmlGuiConfig& config) //throw FfsXmlError +{ + assert(!filenames.empty()); + + std::vector mainCfgs; + std::unique_ptr warning; + + for (auto it = filenames.begin(); it != filenames.end(); ++it) + { + const Zstring& filename = *it; + const bool firstItem = it == filenames.begin(); //init all non-"mainCfg" settings with first config file + + XmlDoc doc; + loadXmlDocument(filename, doc); //throw FfsXmlError + //do NOT use zen::loadStream as it will superfluously load even huge files! + + switch (::getXmlType(doc)) + { + case XML_TYPE_GUI: + { + XmlGuiConfig guiCfg = parseConfig(doc, filename, XML_FORMAT_VER_FFS_GUI, warning); //nothrow + if (firstItem) + config = guiCfg; + else + mainCfgs.push_back(guiCfg.mainCfg); + } + break; + + case XML_TYPE_BATCH: + { + XmlBatchConfig batchCfg = parseConfig(doc, filename, XML_FORMAT_VER_FFS_BATCH, warning); //nothrow + if (firstItem) + config = convertBatchToGui(batchCfg); + else + mainCfgs.push_back(batchCfg.mainCfg); + } + break; + + case XML_TYPE_GLOBAL: + case XML_TYPE_OTHER: + throw FfsXmlError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename))); + } + } + mainCfgs.push_back(config.mainCfg); //save cfg from first line + + config.mainCfg = merge(mainCfgs); + + if (warning) + throw* warning; +} + //################################################################################################ + namespace { void writeConfig(const CompConfig& cmpConfig, XmlOut& out) @@ -1188,8 +1286,8 @@ void writeConfig(const SyncConfig& syncCfg, XmlOut& out) void writeConfig(const FilterConfig& filter, XmlOut& out) { - out["Include"](filter.includeFilter); - out["Exclude"](filter.excludeFilter); + out["Include"](splitFilterByLines(filter.includeFilter)); + out["Exclude"](splitFilterByLines(filter.excludeFilter)); out["TimeSpan"](filter.timeSpan); out["TimeSpan"].attribute("Type", filter.unitTimeSpan); @@ -1300,31 +1398,29 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) { XmlOut outShared = out["Shared"]; - //write program language setting - outShared["Language"](config.programLanguage); + outShared["Language"].attribute("id", config.programLanguage); - outShared["CopyLockedFiles" ](config.copyLockedFiles); - outShared["CopyFilePermissions" ](config.copyFilePermissions); - outShared["TransactionalFileCopy"](config.transactionalFileCopy); - outShared["LockDirectoriesDuringSync"](config.createLockFile); - outShared["VerifyCopiedFiles" ](config.verifyFileCopy); - outShared["RunWithBackgroundPriority"](config.runWithBackgroundPriority); - - //max. allowed file time deviation - outShared["FileTimeTolerance"](config.fileTimeTolerance); - - outShared["LastSyncsFileSizeMax"](config.lastSyncsLogFileSizeMax); + outShared["FailSafeFileCopy" ].attribute("Enabled", config.transactionalFileCopy); + outShared["CopyLockedFiles" ].attribute("Enabled", config.copyLockedFiles); + outShared["CopyFilePermissions" ].attribute("Enabled", config.copyFilePermissions); + outShared["RunWithBackgroundPriority"].attribute("Enabled", config.runWithBackgroundPriority); + outShared["LockDirectoriesDuringSync"].attribute("Enabled", config.createLockFile); + outShared["VerifyCopiedFiles" ].attribute("Enabled", config.verifyFileCopy); + outShared["FileTimeTolerance" ].attribute("Seconds", config.fileTimeTolerance); + outShared["LastSyncsLogSizeMax" ].attribute("Bytes" , config.lastSyncsLogFileSizeMax); XmlOut outOpt = outShared["OptionalDialogs"]; - outOpt["WarnUnresolvedConflicts" ](config.optDialogs.warningUnresolvedConflicts); - outOpt["WarnNotEnoughDiskSpace" ](config.optDialogs.warningNotEnoughDiskSpace); - outOpt["WarnSignificantDifference" ](config.optDialogs.warningSignificantDifference); - outOpt["WarnRecycleBinNotAvailable" ](config.optDialogs.warningRecyclerMissing); - outOpt["WarnDatabaseError" ](config.optDialogs.warningDatabaseError); - outOpt["WarnDependentFolders" ](config.optDialogs.warningDependentFolders); - outOpt["WarnFolderPairRaceCondition"](config.optDialogs.warningFolderPairRaceCondition); - outOpt["PromptSaveConfig" ](config.optDialogs.popupOnConfigChange); - outOpt["ConfirmSyncStart" ](config.optDialogs.confirmSyncStart); + outOpt["WarnUnresolvedConflicts" ].attribute("Enabled", config.optDialogs.warningUnresolvedConflicts); + outOpt["WarnNotEnoughDiskSpace" ].attribute("Enabled", config.optDialogs.warningNotEnoughDiskSpace); + outOpt["WarnSignificantDifference" ].attribute("Enabled", config.optDialogs.warningSignificantDifference); + outOpt["WarnRecycleBinNotAvailable" ].attribute("Enabled", config.optDialogs.warningRecyclerMissing); + outOpt["WarnInputFieldEmpty" ].attribute("Enabled", config.optDialogs.warningInputFieldEmpty); + outOpt["WarnDatabaseError" ].attribute("Enabled", config.optDialogs.warningDatabaseError); + outOpt["WarnDependentFolders" ].attribute("Enabled", config.optDialogs.warningDependentFolders); + outOpt["WarnFolderPairRaceCondition"].attribute("Enabled", config.optDialogs.warningFolderPairRaceCondition); + outOpt["WarnDirectoryLockFailed" ].attribute("Enabled", config.optDialogs.warningDirectoryLockFailed); + outOpt["ConfirmSaveConfig" ].attribute("Enabled", config.optDialogs.popupOnConfigChange); + outOpt["ConfirmStartSync" ].attribute("Enabled", config.optDialogs.confirmSyncStart); //gui specific global settings (optional) XmlOut outGui = out["Gui"]; @@ -1341,18 +1437,20 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) //outManualDel.attribute("DeleteOnBothSides", config.gui.deleteOnBothSides); outManualDel.attribute("UseRecycler" , config.gui.useRecyclerForManualDeletion); - outWnd["CaseSensitiveSearch" ](config.gui.textSearchRespectCase); - outWnd["MaxFolderPairsVisible"](config.gui.maxFolderPairsVisible); + outWnd["CaseSensitiveSearch"].attribute("Enabled", config.gui.textSearchRespectCase); + outWnd["FolderPairsVisible" ].attribute("Max", config.gui.maxFolderPairsVisible); //########################################################### + + XmlOut outOverview = outWnd["OverviewPanel"]; + outOverview.attribute("ShowPercentage", config.gui.showPercentBar); + outOverview.attribute("SortByColumn", config.gui.naviLastSortColumn); + outOverview.attribute("SortAscending", config.gui.naviLastSortAscending); + //write column attributes - XmlOut outColNavi = outWnd["OverviewColumns"]; + XmlOut outColNavi = outOverview["Columns"]; outColNavi(config.gui.columnAttribNavi); - outColNavi.attribute("ShowPercentage", config.gui.showPercentBar); - outColNavi.attribute("SortByColumn", config.gui.naviLastSortColumn); - outColNavi.attribute("SortAscending", config.gui.naviLastSortAscending); - XmlOut outMainGrid = outWnd["MainGrid"]; outMainGrid.attribute("ShowIcons", config.gui.showIcons); outMainGrid.attribute("IconSize", config.gui.iconSize); @@ -1366,7 +1464,9 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) //########################################################### outWnd["ViewFilterDefault"](config.gui.viewFilterDefault); - outWnd["Layout" ](config.gui.guiPerspectiveLast); + outWnd["Perspective" ](config.gui.guiPerspectiveLast); + + outGui["DefaultExclusionFilter"](splitFilterByLines(config.gui.defaultExclusionFilter)); //load config file history outGui["LastUsedConfig"](config.gui.lastUsedConfigFiles); @@ -1392,11 +1492,13 @@ void writeConfig(const XmlGlobalSettings& config, XmlOut& out) template -void writeConfig(const ConfigType& config, XmlType type, const Zstring& filename) +void writeConfig(const ConfigType& config, XmlType type, int xmlFormatVer, const Zstring& filename) { XmlDoc doc("FreeFileSync"); setXmlType(doc, type); //throw() + doc.root().setAttribute("XmlFormat", xmlFormatVer); + XmlOut out(doc); writeConfig(config, out); @@ -1404,21 +1506,21 @@ void writeConfig(const ConfigType& config, XmlType type, const Zstring& filename } } -void xmlAccess::writeConfig(const XmlGuiConfig& config, const Zstring& filename) +void xmlAccess::writeConfig(const XmlGuiConfig& cfg, const Zstring& filename) { - ::writeConfig(config, XML_TYPE_GUI, filename); //throw FfsXmlError + ::writeConfig(cfg, XML_TYPE_GUI, XML_FORMAT_VER_FFS_GUI, filename); //throw FfsXmlError } -void xmlAccess::writeConfig(const XmlBatchConfig& config, const Zstring& filename) +void xmlAccess::writeConfig(const XmlBatchConfig& cfg, const Zstring& filename) { - ::writeConfig(config, XML_TYPE_BATCH, filename); //throw FfsXmlError + ::writeConfig(cfg, XML_TYPE_BATCH, XML_FORMAT_VER_FFS_BATCH, filename); //throw FfsXmlError } -void xmlAccess::writeConfig(const XmlGlobalSettings& config) +void xmlAccess::writeConfig(const XmlGlobalSettings& cfg) { - ::writeConfig(config, XML_TYPE_GLOBAL, utfCvrtTo(getGlobalConfigFile())); //throw FfsXmlError + ::writeConfig(cfg, XML_TYPE_GLOBAL, XML_FORMAT_VER_GLOBAL, getGlobalConfigFile()); //throw FfsXmlError } diff --git a/lib/process_xml.h b/lib/process_xml.h index 9dda330e..8a65d67a 100644 --- a/lib/process_xml.h +++ b/lib/process_xml.h @@ -99,6 +99,8 @@ struct OptionalDialogs bool warningUnresolvedConflicts; bool warningDatabaseError; bool warningRecyclerMissing; + bool warningInputFieldEmpty; + bool warningDirectoryLockFailed; bool popupOnConfigChange; bool confirmSyncStart; }; @@ -124,7 +126,7 @@ struct ViewFilterDefault bool createLeft, createRight, updateLeft, updateRight, deleteLeft, deleteRight, doNothing; //action view }; -wxString getGlobalConfigFile(); +Zstring getGlobalConfigFile(); struct XmlGlobalSettings { @@ -173,6 +175,23 @@ struct XmlGlobalSettings cfgFileHistMax(30), folderHistMax(15), onCompletionHistoryMax(8), +#ifdef FFS_WIN + defaultExclusionFilter(Zstr("\\System Volume Information\\") Zstr("\n") + Zstr("\\$Recycle.Bin\\") Zstr("\n") + Zstr("\\RECYCLER\\") Zstr("\n") + Zstr("\\RECYCLED\\") Zstr("\n") + Zstr("*\\desktop.ini") Zstr("\n") + Zstr("*\\thumbs.db")), +#elif defined FFS_LINUX + defaultExclusionFilter(Zstr("/.Trash-*/") Zstr("\n") + Zstr("/.recycle/")), +#elif defined FFS_MAC + defaultExclusionFilter(Zstr("/.fseventsd/") Zstr("\n") + Zstr("/.Spotlight-V100/") Zstr("\n") + Zstr("/.Trashes/") Zstr("\n") + Zstr("/._.Trashes") Zstr("\n") + Zstr("*/.DS_Store")), +#endif //deleteOnBothSides(false), useRecyclerForManualDeletion(true), //enable if OS supports it; else user will have to activate first and then get an error message #if defined FFS_WIN || defined FFS_MAC @@ -218,10 +237,10 @@ struct XmlGlobalSettings ExternalApps externelApplications; - std::vector cfgFileHistory; + std::vector cfgFileHistory; size_t cfgFileHistMax; - std::vector lastUsedConfigFiles; + std::vector lastUsedConfigFiles; std::vector folderHistoryLeft; std::vector folderHistoryRight; @@ -230,6 +249,8 @@ struct XmlGlobalSettings std::vector onCompletionHistory; size_t onCompletionHistoryMax; + Zstring defaultExclusionFilter; + //bool deleteOnBothSides; bool useRecyclerForManualDeletion; bool textSearchRespectCase; diff --git a/lib/shadow.cpp b/lib/shadow.cpp index 52b40a9e..d3106168 100644 --- a/lib/shadow.cpp +++ b/lib/shadow.cpp @@ -86,7 +86,7 @@ private: //############################################################################################################# -Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) +Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile, const std::function& onBeforeMakeVolumeCopy) { DWORD bufferSize = 10000; std::vector volBuffer(bufferSize); @@ -111,6 +111,7 @@ Zstring ShadowCopy::makeShadowCopy(const Zstring& inputFile) VolNameShadowMap::const_iterator it = shadowVol.find(volumeNamePf); if (it == shadowVol.end()) { + onBeforeMakeVolumeCopy(volumeNamePf); //notify client before (potentially) blocking some time auto newEntry = std::make_shared(volumeNamePf); //throw FileError it = shadowVol.insert(std::make_pair(volumeNamePf, newEntry)).first; } diff --git a/lib/shadow.h b/lib/shadow.h index cbe40dbb..4a05c860 100644 --- a/lib/shadow.h +++ b/lib/shadow.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -20,7 +21,7 @@ class ShadowCopy //take and buffer Windows Volume Shadow Copy snapshots as neede public: ShadowCopy() {} - Zstring makeShadowCopy(const Zstring& inputFile); //throw FileError; returns filename on shadow copy + Zstring makeShadowCopy(const Zstring& inputFile, const std::function& onBeforeMakeVolumeCopy); //throw FileError; returns filename on shadow copy private: ShadowCopy(const ShadowCopy&); diff --git a/lib/soft_filter.h b/lib/soft_filter.h index 32e7888d..1073cd43 100644 --- a/lib/soft_filter.h +++ b/lib/soft_filter.h @@ -76,8 +76,8 @@ inline SoftFilter::SoftFilter(size_t timeSpan, UnitTime unitTimeSpan, size_t sizeMin, UnitSize unitSizeMin, size_t sizeMax, UnitSize unitSizeMax) : - matchesFolder_(unitTimeSpan == UTIME_NONE && - unitSizeMin == USIZE_NONE && + matchesFolder_(unitTimeSpan == UTIME_NONE&& + unitSizeMin == USIZE_NONE&& unitSizeMax == USIZE_NONE) //exclude folders if size or date filter is active: avoids creating empty folders if not needed! { resolveUnits(timeSpan, unitTimeSpan, diff --git a/lib/xml_base.cpp b/lib/xml_base.cpp index e26f73c2..f5091cd6 100644 --- a/lib/xml_base.cpp +++ b/lib/xml_base.cpp @@ -19,28 +19,30 @@ void xmlAccess::loadXmlDocument(const Zstring& filename, XmlDoc& doc) //throw Ff std::string stream; try { + FileInput inputFile(filename); //throw FileError { //quick test whether input is an XML: avoid loading large binary files up front! const std::string xmlBegin = " buffer(xmlBegin.size() + sizeof(zen::BYTE_ORDER_MARK_UTF8)); - - FileInput inputFile(filename); //throw FileError - const size_t bytesRead = inputFile.read(&buffer[0], buffer.size()); //throw FileError + stream.resize(strLength(zen::BYTE_ORDER_MARK_UTF8) + xmlBegin.size()); - const std::string fileBegin(&buffer[0], bytesRead); + const size_t bytesRead = inputFile.read(&stream[0], stream.size()); //throw FileError + stream.resize(bytesRead); - if (!startsWith(fileBegin, xmlBegin) && - !startsWith(fileBegin, zen::BYTE_ORDER_MARK_UTF8 + xmlBegin)) //respect BOM! + if (!startsWith(stream, xmlBegin) && + !startsWith(stream, zen::BYTE_ORDER_MARK_UTF8 + xmlBegin)) //respect BOM! throw FfsXmlError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filename))); } - const zen::UInt64 fs = zen::getFilesize(filename); //throw FileError - stream.resize(to(fs)); + const size_t blockSize = 128 * 1024; + do + { + stream.resize(stream.size() + blockSize); - FileInput inputFile(filename); //throw FileError - const size_t bytesRead = inputFile.read(&stream[0], stream.size()); //throw FileError - if (bytesRead < to(fs)) - throw FfsXmlError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename))); + const size_t bytesRead = inputFile.read(&*stream.begin() + stream.size() - blockSize, blockSize); //throw FileError + if (bytesRead < blockSize) + stream.resize(stream.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + } + while (!inputFile.eof()); } catch (const FileError& error) { @@ -62,16 +64,14 @@ void xmlAccess::loadXmlDocument(const Zstring& filename, XmlDoc& doc) //throw Ff } -const std::wstring xmlAccess::getErrorMessageFormatted(const XmlIn& in) +const std::wstring xmlAccess::getErrorMessageFormatted(const std::vector& failedElements) { std::wstring msg; - const auto& failedElements = in.getErrorsAs(); if (!failedElements.empty()) { msg = _("Cannot read the following XML elements:") + L"\n"; - std::for_each(failedElements.begin(), failedElements.end(), - [&](const std::wstring& elem) { msg += L"\n" + elem; }); + std::for_each(failedElements.begin(), failedElements.end(), [&](const std::wstring& elem) { msg += L"\n" + elem; }); } return msg; diff --git a/lib/xml_base.h b/lib/xml_base.h index 338f91f7..ceaf609b 100644 --- a/lib/xml_base.h +++ b/lib/xml_base.h @@ -36,7 +36,7 @@ private: void saveXmlDocument(const zen::XmlDoc& doc, const Zstring& filename); //throw FfsXmlError void loadXmlDocument(const Zstring& filename, zen::XmlDoc& doc); //throw FfsXmlError -const std::wstring getErrorMessageFormatted(const zen::XmlIn& in); +const std::wstring getErrorMessageFormatted(const std::vector& failedElements); } #endif // XMLBASE_H_INCLUDED -- cgit