diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:06 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:06 +0200 |
commit | fbe76102e941b9f1edaf236788e42678f05fdf9a (patch) | |
tree | f5f538316019fa89be8dc478103490c3a826f3ac /library/db_file.cpp | |
parent | 3.8 (diff) | |
download | FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.gz FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.bz2 FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.zip |
3.9
Diffstat (limited to 'library/db_file.cpp')
-rw-r--r-- | library/db_file.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/library/db_file.cpp b/library/db_file.cpp new file mode 100644 index 00000000..1daa51f5 --- /dev/null +++ b/library/db_file.cpp @@ -0,0 +1,486 @@ +// ************************************************************************** +// * 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-2010 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +// +#include "db_file.h" +#include <wx/wfstream.h> +#include <wx/zstream.h> +#include "../shared/global_func.h" +#include "../shared/file_error.h" +#include <wx/intl.h> +#include "../shared/string_conv.h" +#include "../shared/file_handling.h" +#include <wx/mstream.h> +#include "../shared/serialize.h" +#include "../shared/file_io.h" +#include "../shared/loki/ScopeGuard.h" + +#ifdef FFS_WIN +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "../shared/long_path_prefix.h" +#endif + +using namespace ffs3; + + +namespace +{ +//------------------------------------------------------------------------------------------------------------------------------- +const char FILE_FORMAT_DESCR[] = "FreeFileSync"; +const int FILE_FORMAT_VER = 4; +//------------------------------------------------------------------------------------------------------------------------------- + + +//yet another layer of indirection: Since 32/64 bit builds are binary incompatible, we want them to write into "distinct parts" of the db-file +//just like we were actually accessing different files +class FileInputStreamDB : public FileInputStream +{ +public: + FileInputStreamDB(const Zstring& filename) : //throw FileError() + FileInputStream(filename) + { + //read FreeFileSync file identifier + char formatDescr[sizeof(FILE_FORMAT_DESCR)]; + Read(formatDescr, sizeof(formatDescr)); //throw (FileError) + formatDescr[sizeof(formatDescr) - 1] = 0; + + if (std::string(formatDescr) != FILE_FORMAT_DESCR) + throw FileError(wxString(_("Incompatible synchronization database format:")) + wxT(" \n") + wxT("\"") + zToWx(filename) + wxT("\"")); + } + +private: +}; + + +class FileOutputStreamDB : public FileOutputStream +{ +public: + FileOutputStreamDB(const Zstring& filename) : //throw FileError() + FileOutputStream(filename) + { + //write FreeFileSync file identifier + Write(FILE_FORMAT_DESCR, sizeof(FILE_FORMAT_DESCR)); //throw (FileError) + } + +private: +}; +} +//####################################################################################################################################### + + + + +class ReadDirInfo : public util::ReadInputStream +{ +public: + ReadDirInfo(wxInputStream& stream, const wxString& errorObjName, DirInformation& dirInfo) : ReadInputStream(stream, errorObjName) + { + //read filter settings + dirInfo.filter = BaseFilter::loadFilter(getStream()); + check(); + + //start recursion + execute(dirInfo.baseDirContainer); + } + +private: + void execute(DirContainer& dirCont) const + { + size_t fileCount = readNumberC<size_t>(); + while (fileCount-- != 0) + readSubFile(dirCont); + + size_t symlinkCount = readNumberC<size_t>(); + while (symlinkCount-- != 0) + readSubLink(dirCont); + + size_t dirCount = readNumberC<size_t>(); + while (dirCount-- != 0) + readSubDirectory(dirCont); + } + + void readSubFile(DirContainer& dirCont) const + { + //attention: order of function argument evaluation is undefined! So do it one after the other... + const Zstring shortName = readStringC(); //file name + + const long modHigh = readNumberC<long>(); + const unsigned long modLow = readNumberC<unsigned long>(); + + const unsigned long sizeHigh = readNumberC<unsigned long>(); + const unsigned long sizeLow = readNumberC<unsigned long>(); + + //const util::FileID fileIdentifier(stream_); + //check(); + + dirCont.addSubFile(shortName, + FileDescriptor(wxLongLong(modHigh, modLow), + wxULongLong(sizeHigh, sizeLow))); + } + + + void readSubLink(DirContainer& dirCont) const + { + //attention: order of function argument evaluation is undefined! So do it one after the other... + const Zstring shortName = readStringC(); //file name + const long modHigh = readNumberC<long>(); + const unsigned long modLow = readNumberC<unsigned long>(); + const Zstring targetPath = readStringC(); //file name + const LinkDescriptor::LinkType linkType = static_cast<LinkDescriptor::LinkType>(readNumberC<int>()); + + dirCont.addSubLink(shortName, + LinkDescriptor(wxLongLong(modHigh, modLow), targetPath, linkType)); + } + + + void readSubDirectory(DirContainer& dirCont) const + { + const Zstring shortName = readStringC(); //directory name + DirContainer& subDir = dirCont.addSubDir(shortName); + execute(subDir); //recurse + } +}; + + +typedef boost::shared_ptr<std::vector<char> > MemoryStreamPtr; //byte stream representing DirInformation +typedef std::map<util::UniqueId, MemoryStreamPtr> DirectoryTOC; //list of streams ordered by a UUID pointing to their partner database +typedef std::pair<util::UniqueId, DirectoryTOC> DbStreamData; //header data: UUID representing this database, item data: list of dir-streams +/* Example +left side right side +--------- ---------- +DB-ID 123 <-\ /-> DB-ID 567 + \/ +Partner-ID 111 /\ Partner-ID 222 +Partner-ID 567 _/ \_ Partner-ID 123 + ... ... +*/ + +class ReadFileStream : public util::ReadInputStream +{ +public: + ReadFileStream(wxInputStream& stream, const wxString& filename, DbStreamData& output) : ReadInputStream(stream, filename) + { + if (readNumberC<int>() != FILE_FORMAT_VER) //read file format version + throw FileError(wxString(_("Incompatible synchronization database format:")) + wxT(" \n") + wxT("\"") + filename + wxT("\"")); + + //read DB id + output.first = util::UniqueId(getStream()); + check(); + + DirectoryTOC& dbList = output.second; + dbList.clear(); + + size_t dbCount = readNumberC<size_t>(); //number of databases: one for each sync-pair + while (dbCount-- != 0) + { + const util::UniqueId partnerID(getStream()); //DB id of partner databases + check(); + + CharArray buffer = readArrayC(); //read db-entry stream (containing DirInformation) + + dbList.insert(std::make_pair(partnerID, buffer)); + } + } +}; + + +DbStreamData loadFile(const Zstring& filename) //throw (FileError) +{ + if (!ffs3::fileExists(filename)) + throw FileError(wxString(_("Initial synchronization:")) + wxT(" \n\n") + + _("One of the FreeFileSync database files is not yet existing:") + wxT(" \n") + + wxT("\"") + zToWx(filename) + wxT("\"")); + + //read format description (uncompressed) + FileInputStreamDB uncompressed(filename); //throw (FileError) + + wxZlibInputStream input(uncompressed, wxZLIB_ZLIB); + + DbStreamData output; + ReadFileStream(input, zToWx(filename), output); + return output; +} + + +std::pair<DirInfoPtr, DirInfoPtr> ffs3::loadFromDisk(const BaseDirMapping& baseMapping) //throw (FileError) +{ + const Zstring fileNameLeft = baseMapping.getDBFilename<LEFT_SIDE>(); + const Zstring fileNameRight = baseMapping.getDBFilename<RIGHT_SIDE>(); + + try + { + //read file data: db ID + mapping of partner-ID/DirInfo-stream + const DbStreamData dbEntriesLeft = ::loadFile(fileNameLeft); + const DbStreamData dbEntriesRight = ::loadFile(fileNameRight); + + //find associated DirInfo-streams + DirectoryTOC::const_iterator dbLeft = dbEntriesLeft.second.find(dbEntriesRight.first); //find left db-entry that corresponds to right database + if (dbLeft == dbEntriesLeft.second.end()) + throw FileError(wxString(_("Initial synchronization:")) + wxT(" \n\n") + + _("One of the FreeFileSync database entries within the following file is not yet existing:") + wxT(" \n") + + wxT("\"") + zToWx(fileNameLeft) + wxT("\"")); + + DirectoryTOC::const_iterator dbRight = dbEntriesRight.second.find(dbEntriesLeft.first); //find left db-entry that corresponds to right database + if (dbRight == dbEntriesRight.second.end()) + throw FileError(wxString(_("Initial synchronization:")) + wxT(" \n\n") + + _("One of the FreeFileSync database entries within the following file is not yet existing:") + wxT(" \n") + + wxT("\"") + zToWx(fileNameRight) + wxT("\"")); + + //read streams into DirInfo + boost::shared_ptr<DirInformation> dirInfoLeft(new DirInformation); + wxMemoryInputStream buffer(&(*dbLeft->second)[0], dbLeft->second->size()); //convert char-array to inputstream: no copying, ownership not transferred + ReadDirInfo(buffer, zToWx(fileNameLeft), *dirInfoLeft); //read file/dir information + + boost::shared_ptr<DirInformation> dirInfoRight(new DirInformation); + wxMemoryInputStream buffer2(&(*dbRight->second)[0], dbRight->second->size()); //convert char-array to inputstream: no copying, ownership not transferred + ReadDirInfo(buffer2, zToWx(fileNameRight), *dirInfoRight); //read file/dir information + + return std::make_pair(dirInfoLeft, dirInfoRight); + } + catch (const std::bad_alloc&) //this is most likely caused by a corrupted database file + { + throw FileError(wxString(_("Error reading from synchronization database:")) + wxT(" (bad_alloc)")); + } +} + + +//------------------------------------------------------------------------------------------------------------------------- + +template <SelectedSide side> +struct IsNonEmpty +{ + bool operator()(const FileSystemObject& fsObj) const + { + return !fsObj.isEmpty<side>(); + } +}; + + +template <SelectedSide side> +class SaveDirInfo : public util::WriteOutputStream +{ +public: + SaveDirInfo(const BaseDirMapping& baseMapping, const wxString& errorObjName, wxOutputStream& stream) : WriteOutputStream(errorObjName, stream) + { + //save filter settings + baseMapping.getFilter()->saveFilter(getStream()); + check(); + + //start recursion + execute(baseMapping); + } + +private: + friend class util::ProxyForEach<SaveDirInfo<side> >; //friend declaration of std::for_each is NOT sufficient as implementation is compiler dependent! + + void execute(const HierarchyObject& hierObj) + { + util::ProxyForEach<SaveDirInfo<side> > prx(*this); //grant std::for_each access to private parts of this class + + writeNumberC<size_t>(std::count_if(hierObj.useSubFiles().begin(), hierObj.useSubFiles().end(), IsNonEmpty<side>())); //number of (existing) files + std::for_each(hierObj.useSubFiles().begin(), hierObj.useSubFiles().end(), prx); + + writeNumberC<size_t>(std::count_if(hierObj.useSubLinks().begin(), hierObj.useSubLinks().end(), IsNonEmpty<side>())); //number of (existing) files + std::for_each(hierObj.useSubLinks().begin(), hierObj.useSubLinks().end(), prx); + + writeNumberC<size_t>(std::count_if(hierObj.useSubDirs().begin(), hierObj.useSubDirs().end(), IsNonEmpty<side>())); //number of (existing) directories + std::for_each(hierObj.useSubDirs().begin(), hierObj.useSubDirs().end(), prx); + } + + void operator()(const FileMapping& fileMap) + { + if (!fileMap.isEmpty<side>()) + { + writeStringC(fileMap.getObjShortName()); //file name + writeNumberC<long>( fileMap.getLastWriteTime<side>().GetHi()); //last modification time + writeNumberC<unsigned long>(fileMap.getLastWriteTime<side>().GetLo()); // + writeNumberC<unsigned long>(fileMap.getFileSize<side>().GetHi()); //filesize + writeNumberC<unsigned long>(fileMap.getFileSize<side>().GetLo()); // + + //fileMap.getFileID<side>().toStream(stream_); //unique file identifier + //check(); + } + } + + void operator()(const SymLinkMapping& linkObj) + { + if (!linkObj.isEmpty<side>()) + { + writeStringC(linkObj.getObjShortName()); + writeNumberC<long>( linkObj.getLastWriteTime<side>().GetHi()); //last modification time + writeNumberC<unsigned long>(linkObj.getLastWriteTime<side>().GetLo()); // + writeStringC(linkObj.getTargetPath<side>()); + writeNumberC<int>(linkObj.getLinkType<side>()); + } + } + + void operator()(const DirMapping& dirMap) + { + if (!dirMap.isEmpty<side>()) + { + writeStringC(dirMap.getObjShortName()); //directory name + execute(dirMap); //recurse + } + } +}; + + +class WriteFileStream : public util::WriteOutputStream +{ +public: + WriteFileStream(const DbStreamData& input, const wxString& filename, wxOutputStream& stream) : WriteOutputStream(filename, stream) + { + //save file format version + writeNumberC<int>(FILE_FORMAT_VER); + + //write DB id + input.first.toStream(getStream()); + check(); + + const DirectoryTOC& dbList = input.second; + + writeNumberC<size_t>(dbList.size()); //number of database records: one for each sync-pair + + for (DirectoryTOC::const_iterator i = dbList.begin(); i != dbList.end(); ++i) + { + i->first.toStream(getStream()); //DB id of partner database + check(); + + writeArrayC(*(i->second)); //write DirInformation stream + } + } +}; + + +//save/load DirContainer +void saveFile(const DbStreamData& dbStream, const Zstring& filename) //throw (FileError) +{ + { + //write format description (uncompressed) + FileOutputStreamDB uncompressed(filename); //throw (FileError) + + wxZlibOutputStream output(uncompressed, 4, wxZLIB_ZLIB); + /* 4 - best compromise between speed and compression: (scanning 200.000 objects) + 0 (uncompressed) 8,95 MB - 422 ms + 2 2,07 MB - 470 ms + 4 1,87 MB - 500 ms + 6 1,77 MB - 613 ms + 9 (maximal compression) 1,74 MB - 3330 ms */ + + WriteFileStream(dbStream, zToWx(filename), output); + } + //(try to) hide database file +#ifdef FFS_WIN + ::SetFileAttributes(ffs3::applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_HIDDEN); +#endif +} + + +bool entryExisting(const DirectoryTOC& table, const util::UniqueId& newKey, const MemoryStreamPtr& newValue) +{ + DirectoryTOC::const_iterator iter = table.find(newKey); + if (iter == table.end()) + return false; + + if (!iter->second.get()) + return !newValue.get(); + + if (!newValue.get()) + return false; + + return newValue->size() == iter->second->size() && std::equal(newValue->begin(), newValue->end(), iter->second->begin()); +} + + +void ffs3::saveToDisk(const BaseDirMapping& baseMapping) //throw (FileError) +{ + //transactional behaviour! write to tmp files first + const Zstring fileNameLeftTmp = baseMapping.getDBFilename<LEFT_SIDE>() + DefaultStr(".tmp"); + const Zstring fileNameRightTmp = baseMapping.getDBFilename<RIGHT_SIDE>() + DefaultStr(".tmp");; + + //delete old tmp file, if necessary -> throws if deletion fails! + removeFile(fileNameLeftTmp); // + removeFile(fileNameRightTmp); //throw (FileError) + + //load old database files... + + //read file data: db ID + mapping of partner-ID/DirInfo-stream: may throw! + DbStreamData dbEntriesLeft; + if (ffs3::fileExists(baseMapping.getDBFilename<LEFT_SIDE>())) + try + { + dbEntriesLeft = ::loadFile(baseMapping.getDBFilename<LEFT_SIDE>()); + } + catch(FileError&) {} //if error occurs: just overwrite old file! User is already informed about issues right after comparing! + //else -> dbEntriesLeft has empty mapping, but already a DB-ID! + + //read file data: db ID + mapping of partner-ID/DirInfo-stream: may throw! + DbStreamData dbEntriesRight; + if (ffs3::fileExists(baseMapping.getDBFilename<RIGHT_SIDE>())) + try + { + dbEntriesRight = ::loadFile(baseMapping.getDBFilename<RIGHT_SIDE>()); + } + catch(FileError&) {} //if error occurs: just overwrite old file! User is already informed about issues right after comparing! + + //create new database entries + MemoryStreamPtr dbEntryLeft(new std::vector<char>); + { + wxMemoryOutputStream buffer; + SaveDirInfo<LEFT_SIDE>(baseMapping, zToWx(baseMapping.getDBFilename<LEFT_SIDE>()), buffer); + dbEntryLeft->resize(buffer.GetSize()); //convert output stream to char-array + buffer.CopyTo(&(*dbEntryLeft)[0], buffer.GetSize()); // + } + + MemoryStreamPtr dbEntryRight(new std::vector<char>); + { + wxMemoryOutputStream buffer; + SaveDirInfo<RIGHT_SIDE>(baseMapping, zToWx(baseMapping.getDBFilename<RIGHT_SIDE>()), buffer); + dbEntryRight->resize(buffer.GetSize()); //convert output stream to char-array + buffer.CopyTo(&(*dbEntryRight)[0], buffer.GetSize()); // + } + + //create/update DirInfo-streams + { + const bool updateRequiredLeft = !entryExisting(dbEntriesLeft. second, dbEntriesRight.first, dbEntryLeft); + const bool updateRequiredRight = !entryExisting(dbEntriesRight.second, dbEntriesLeft. first, dbEntryRight); + //some users monitor the *.ffs_db file with RTS => don't touch the file if it isnt't strictly needed + if (!updateRequiredLeft && !updateRequiredRight) + return; + } + + dbEntriesLeft.second[dbEntriesRight.first] = dbEntryLeft; + dbEntriesRight.second[dbEntriesLeft.first] = dbEntryRight; + + + struct TryCleanUp //ensure cleanup if working with temporary failed! + { + static void tryDeleteFile(const Zstring& filename) //throw () + { + try + { + removeFile(filename); + } + catch (...) {} + } + }; + + //write (temp-) files... + Loki::ScopeGuard guardTempFileLeft = Loki::MakeGuard(&TryCleanUp::tryDeleteFile, fileNameLeftTmp); + saveFile(dbEntriesLeft, fileNameLeftTmp); //throw (FileError) + + Loki::ScopeGuard guardTempFileRight = Loki::MakeGuard(&TryCleanUp::tryDeleteFile, fileNameRightTmp); + saveFile(dbEntriesRight, fileNameRightTmp); //throw (FileError) + + //operation finished: rename temp files -> this should work transactionally: + //if there were no write access, creation of temp files would have failed + removeFile(baseMapping.getDBFilename<LEFT_SIDE>()); + removeFile(baseMapping.getDBFilename<RIGHT_SIDE>()); + renameFile(fileNameLeftTmp, baseMapping.getDBFilename<LEFT_SIDE>()); //throw (FileError); + renameFile(fileNameRightTmp, baseMapping.getDBFilename<RIGHT_SIDE>()); //throw (FileError); + + guardTempFileLeft. Dismiss(); //no need to delete temp file anymore + guardTempFileRight.Dismiss(); // +} |