summaryrefslogtreecommitdiff
path: root/library/db_file.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'library/db_file.cpp')
-rw-r--r--library/db_file.cpp598
1 files changed, 0 insertions, 598 deletions
diff --git a/library/db_file.cpp b/library/db_file.cpp
deleted file mode 100644
index 268e411e..00000000
--- a/library/db_file.cpp
+++ /dev/null
@@ -1,598 +0,0 @@
-// **************************************************************************
-// * 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 "db_file.h"
-#include <wx/wfstream.h>
-#include <wx/zstream.h>
-#include <wx/mstream.h>
-#include "../shared/global_func.h"
-#include "../shared/file_error.h"
-#include "../shared/string_conv.h"
-#include "../shared/file_handling.h"
-#include "../shared/serialize.h"
-#include "../shared/file_io.h"
-#include "../shared/loki/ScopeGuard.h"
-#include "../shared/i18n.h"
-#include <boost/bind.hpp>
-
-#ifdef FFS_WIN
-#include <wx/msw/wrapwin.h> //includes "windows.h"
-#include "../shared/long_path_prefix.h"
-#endif
-
-using namespace zen;
-
-
-namespace
-{
-//-------------------------------------------------------------------------------------------------------------------------------
-const char FILE_FORMAT_DESCR[] = "FreeFileSync";
-const int FILE_FORMAT_VER = 7;
-//-------------------------------------------------------------------------------------------------------------------------------
-
-
-template <SelectedSide side> inline
-Zstring getDBFilename(const BaseDirMapping& baseMap, bool tempfile = false)
-{
- //Linux and Windows builds are binary incompatible: char/wchar_t case, sensitive/insensitive
- //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 not be included into comparison when located in base sync directories
-#ifdef FFS_WIN
- Zstring dbname = Zstring(Zstr("sync")) + (tempfile ? Zstr(".tmp") : Zstr("")) + SYNC_DB_FILE_ENDING;
-#elif defined FFS_LINUX
- //files beginning with dots are hidden e.g. in Nautilus
- Zstring dbname = Zstring(Zstr(".sync")) + (tempfile ? Zstr(".tmp") : Zstr("")) + SYNC_DB_FILE_ENDING;
-#endif
-
- return baseMap.getBaseDirPf<side>() + dbname;
-}
-
-
-
-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
-
- if (!std::equal(FILE_FORMAT_DESCR, FILE_FORMAT_DESCR + sizeof(FILE_FORMAT_DESCR), formatDescr))
- throw FileError(_("Incompatible synchronization database format:") + " \n" + "\"" + filename + "\"");
- }
-
-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 zen::ReadInputStream
-{
-public:
- ReadDirInfo(wxInputStream& stream, const wxString& errorObjName, DirInformation& dirInfo) : ReadInputStream(stream, errorObjName)
- {
- //|-------------------------------------------------------------------------------------
- //| ensure 32/64 bit portability: use fixed size data types only e.g. boost::uint32_t |
- //|-------------------------------------------------------------------------------------
-
- //read filter settings -> currently not required, but persisting it doesn't hurt
- dirInfo.filter = HardFilter::loadFilter(getStream());
- check();
-
- //start recursion
- execute(dirInfo.baseDirContainer);
- }
-
-private:
- void execute(DirContainer& dirCont) const
- {
- while (readNumberC<bool>())
- readSubFile(dirCont);
-
- while (readNumberC<bool>())
- readSubLink(dirCont);
-
- while (readNumberC<bool>())
- 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<Zstring>(); //file name
-
- const boost::int64_t modTime = readNumberC<boost::int64_t>();
- const boost::uint64_t fileSize = readNumberC<boost::uint64_t>();
-
- //const util::FileID fileIdentifier(stream_);
- //check();
-
- dirCont.addSubFile(shortName,
- FileDescriptor(modTime, fileSize));
- }
-
-
- void readSubLink(DirContainer& dirCont) const
- {
- //attention: order of function argument evaluation is undefined! So do it one after the other...
- const Zstring shortName = readStringC<Zstring>(); //file name
- const boost::int64_t modTime = readNumberC<boost::int64_t>();
- const Zstring targetPath = readStringC<Zstring>(); //file name
- const LinkDescriptor::LinkType linkType = static_cast<LinkDescriptor::LinkType>(readNumberC<boost::int32_t>());
-
- dirCont.addSubLink(shortName,
- LinkDescriptor(modTime, targetPath, linkType));
- }
-
-
- void readSubDirectory(DirContainer& dirCont) const
- {
- const Zstring shortName = readStringC<Zstring>(); //directory name
- DirContainer& subDir = dirCont.addSubDir(shortName);
- execute(subDir); //recurse
- }
-};
-
-namespace
-{
-typedef std::string UniqueId;
-typedef std::shared_ptr<std::vector<char> > MemoryStreamPtr; //byte stream representing DirInformation
-typedef std::map<UniqueId, MemoryStreamPtr> StreamMapping; //list of streams ordered by session UUID
-}
-
-class ReadFileStream : public zen::ReadInputStream
-{
-public:
- ReadFileStream(wxInputStream& stream, const wxString& filename, StreamMapping& streamList) : ReadInputStream(stream, filename)
- {
- //|-------------------------------------------------------------------------------------
- //| ensure 32/64 bit portability: used fixed size data types only e.g. boost::uint32_t |
- //|-------------------------------------------------------------------------------------
-
- boost::int32_t version = readNumberC<boost::int32_t>();
-
- if (version != FILE_FORMAT_VER) //read file format version
- throw FileError(_("Incompatible synchronization database format:") + " \n" + "\"" + filename.c_str() + "\"");
-
- streamList.clear();
-
- boost::uint32_t dbCount = readNumberC<boost::uint32_t>(); //number of databases: one for each sync-pair
- while (dbCount-- != 0)
- {
- //DB id of partner databases
- const CharArray tmp2 = readArrayC();
- const std::string sessionID(tmp2->begin(), tmp2->end());
-
- CharArray buffer = readArrayC(); //read db-entry stream (containing DirInformation)
-
- streamList.insert(std::make_pair(sessionID, buffer));
- }
- }
-};
-
-namespace
-{
-StreamMapping loadStreams(const Zstring& filename) //throw FileError
-{
- if (!zen::fileExists(filename))
- throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + " \n\n" +
- _("One of the FreeFileSync database files is not yet existing:") + " \n" +
- "\"" + filename + "\"");
-
- try
- {
- //read format description (uncompressed)
- FileInputStreamDB uncompressed(filename); //throw FileError
-
- wxZlibInputStream input(uncompressed, wxZLIB_ZLIB);
-
- StreamMapping streamList;
- ReadFileStream(input, toWx(filename), streamList);
- return streamList;
- }
- catch (const std::bad_alloc&) //this is most likely caused by a corrupted database file
- {
- throw FileError(_("Error reading from synchronization database:") + " (bad_alloc)");
- }
-}
-
-
-DirInfoPtr parseStream(const std::vector<char>& stream, const Zstring& fileName) //throw FileError -> return value always bound!
-{
- try
- {
- //read streams into DirInfo
- auto dirInfo = std::make_shared<DirInformation>();
- wxMemoryInputStream buffer(&stream[0], stream.size()); //convert char-array to inputstream: no copying, ownership not transferred
- ReadDirInfo(buffer, toWx(fileName), *dirInfo); //throw FileError
- return dirInfo;
- }
- catch (const std::bad_alloc&) //this is most likely caused by a corrupted database file
- {
- throw FileError(_("Error reading from synchronization database:") + " (bad_alloc)");
- }
-}
-}
-
-
-std::pair<DirInfoPtr, DirInfoPtr> zen::loadFromDisk(const BaseDirMapping& baseMapping) //throw FileError
-{
- const Zstring fileNameLeft = getDBFilename<LEFT_SIDE>(baseMapping);
- const Zstring fileNameRight = getDBFilename<RIGHT_SIDE>(baseMapping);
-
- //read file data: list of session ID + DirInfo-stream
- const StreamMapping streamListLeft = ::loadStreams(fileNameLeft); //throw FileError
- const StreamMapping streamListRight = ::loadStreams(fileNameRight); //throw FileError
-
- //find associated session: there can be at most one session within intersection of left and right ids
- StreamMapping::const_iterator streamLeft = streamListLeft .end();
- StreamMapping::const_iterator streamRight = streamListRight.end();
- for (auto iterLeft = streamListLeft.begin(); iterLeft != streamListLeft.end(); ++iterLeft)
- {
- auto iterRight = streamListRight.find(iterLeft->first);
- if (iterRight != streamListRight.end())
- {
- streamLeft = iterLeft;
- streamRight = iterRight;
- break;
- }
- }
-
- if (streamLeft == streamListLeft .end() ||
- streamRight == streamListRight.end() ||
- !streamLeft ->second.get() ||
- !streamRight->second.get())
- throw FileErrorDatabaseNotExisting(_("Initial synchronization:") + " \n\n" +
- _("Database files do not share a common synchronization session:") + " \n" +
- "\"" + fileNameLeft + "\"\n" +
- "\"" + fileNameRight + "\"");
- //read streams into DirInfo
- DirInfoPtr dirInfoLeft = parseStream(*streamLeft ->second, fileNameLeft); //throw FileError
- DirInfoPtr dirInfoRight = parseStream(*streamRight->second, fileNameRight); //throw FileError
-
- return std::make_pair(dirInfoLeft, dirInfoRight);
-}
-
-
-//-------------------------------------------------------------------------------------------------------------------------
-template <SelectedSide side>
-class SaveDirInfo : public WriteOutputStream
-{
-public:
- SaveDirInfo(const BaseDirMapping& baseMapping, const DirContainer* oldDirInfo, const wxString& errorObjName, wxOutputStream& stream) : WriteOutputStream(errorObjName, stream)
- {
- //save filter settings
- baseMapping.getFilter()->saveFilter(getStream());
- check();
-
- //start recursion
- execute(baseMapping, oldDirInfo);
- }
-
-private:
- void execute(const HierarchyObject& hierObj, const DirContainer* oldDirInfo)
- {
- std::for_each(hierObj.refSubFiles().begin(), hierObj.refSubFiles().end(), boost::bind(&SaveDirInfo::processFile, this, _1, oldDirInfo));
- writeNumberC<bool>(false); //mark last entry
- std::for_each(hierObj.refSubLinks().begin(), hierObj.refSubLinks().end(), boost::bind(&SaveDirInfo::processLink, this, _1, oldDirInfo));
- writeNumberC<bool>(false); //mark last entry
- std::for_each(hierObj.refSubDirs ().begin(), hierObj.refSubDirs ().end(), boost::bind(&SaveDirInfo::processDir, this, _1, oldDirInfo));
- writeNumberC<bool>(false); //mark last entry
- }
-
- void processFile(const FileMapping& fileMap, const DirContainer* oldParentDir)
- {
- if (fileMap.getCategory() == FILE_EQUAL) //data in sync: write current state
- {
- if (!fileMap.isEmpty<side>())
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(fileMap.getShortName<side>()); //save respecting case! (Windows)
- writeNumberC<boost::int64_t >(to<boost::int64_t>(fileMap.getLastWriteTime<side>())); //last modification time
- writeNumberC<boost::uint64_t>(to<boost::uint64_t>(fileMap.getFileSize<side>())); //filesize
- }
- }
- else //not in sync: reuse last synchronous state
- {
- if (oldParentDir) //no data is also a "synchronous state"!
- {
- auto iter = oldParentDir->files.find(fileMap.getObjShortName());
- if (iter != oldParentDir->files.end())
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(iter->first); //save respecting case! (Windows)
- writeNumberC<boost::int64_t >(to<boost::int64_t>(iter->second.lastWriteTimeRaw)); //last modification time
- writeNumberC<boost::uint64_t>(to<boost::uint64_t>(iter->second.fileSize)); //filesize
- }
- }
- }
- }
-
- void processLink(const SymLinkMapping& linkObj, const DirContainer* oldParentDir)
- {
- if (linkObj.getLinkCategory() == SYMLINK_EQUAL) //data in sync: write current state
- {
- if (!linkObj.isEmpty<side>())
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(linkObj.getShortName<side>()); //save respecting case! (Windows)
- writeNumberC<boost::int64_t>(to<boost::int64_t>(linkObj.getLastWriteTime<side>())); //last modification time
- writeStringC(linkObj.getTargetPath<side>());
- writeNumberC<boost::int32_t>(linkObj.getLinkType<side>());
- }
- }
- else //not in sync: reuse last synchronous state
- {
- if (oldParentDir) //no data is also a "synchronous state"!
- {
- auto iter = oldParentDir->links.find(linkObj.getObjShortName());
- if (iter != oldParentDir->links.end())
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(iter->first); //save respecting case! (Windows)
- writeNumberC<boost::int64_t>(to<boost::int64_t>(iter->second.lastWriteTimeRaw)); //last modification time
- writeStringC(iter->second.targetPath);
- writeNumberC<boost::int32_t>(iter->second.type);
- }
- }
- }
- }
-
- void processDir(const DirMapping& dirMap, const DirContainer* oldParentDir)
- {
- const DirContainer* oldDir = NULL;
- const Zstring* oldDirName = NULL;
- if (oldParentDir) //no data is also a "synchronous state"!
- {
- auto iter = oldParentDir->dirs.find(dirMap.getObjShortName());
- if (iter != oldParentDir->dirs.end())
- {
- oldDirName = &iter->first;
- oldDir = &iter->second;
- }
- }
-
- CompareDirResult cat = dirMap.getDirCategory();
-
- if (cat == DIR_EQUAL) //data in sync: write current state
- {
- if (!dirMap.isEmpty<side>())
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(dirMap.getShortName<side>()); //save respecting case! (Windows)
- execute(dirMap, oldDir); //recurse
- }
- }
- else //not in sync: reuse last synchronous state
- {
- if (oldDir)
- {
- writeNumberC<bool>(true); //mark beginning of entry
- writeStringC(*oldDirName); //save respecting case! (Windows)
- execute(dirMap, oldDir); //recurse
- return;
- }
- //no data is also a "synchronous state"!
-
- //else: not in sync AND no "last synchronous state"
- //we cannot simply skip the whole directory, since sub-items might be in sync
- //Example: directories on left and right differ in case while sub-files are equal
- switch (cat)
- {
- case DIR_LEFT_SIDE_ONLY: //sub-items cannot be in sync
- break;
- case DIR_RIGHT_SIDE_ONLY: //sub-items cannot be in sync
- break;
- case DIR_EQUAL:
- assert(false);
- break;
- case DIR_DIFFERENT_METADATA:
- writeNumberC<bool>(true);
- writeStringC(dirMap.getShortName<side>());
- //ATTENTION: strictly this is a violation of the principle of reporting last synchronous state!
- //however in this case this will result in "last sync unsuccessful" for this directory within <automatic> algorithm, which is fine
- execute(dirMap, oldDir); //recurse and save sub-items which are in sync
- break;
- }
- }
- }
-};
-
-
-class WriteFileStream : public WriteOutputStream
-{
-public:
- WriteFileStream(const StreamMapping& streamList, const wxString& filename, wxOutputStream& stream) : WriteOutputStream(filename, stream)
- {
- //save file format version
- writeNumberC<boost::int32_t>(FILE_FORMAT_VER);
-
- writeNumberC<boost::uint32_t>(static_cast<boost::uint32_t>(streamList.size())); //number of database records: one for each sync-pair
-
- for (StreamMapping::const_iterator i = streamList.begin(); i != streamList.end(); ++i)
- {
- //sync session id
- writeArrayC(std::vector<char>(i->first.begin(), i->first.end()));
-
- //write DirInformation stream
- writeArrayC(*(i->second));
- }
- }
-};
-
-
-//save/load DirContainer
-void saveFile(const StreamMapping& streamList, 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(streamList, toWx(filename), output);
- }
- //(try to) hide database file
-#ifdef FFS_WIN
- ::SetFileAttributes(zen::applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_HIDDEN);
-#endif
-}
-
-
-bool equalEntry(const MemoryStreamPtr& lhs, const MemoryStreamPtr& rhs)
-{
- if (!lhs.get() || !rhs.get())
- return lhs.get() == rhs.get();
-
- return *lhs == *rhs;
-}
-
-
-void zen::saveToDisk(const BaseDirMapping& baseMapping) //throw FileError
-{
- //transactional behaviour! write to tmp files first
- const Zstring dbNameLeftTmp = getDBFilename<LEFT_SIDE >(baseMapping, true);
- const Zstring dbNameRightTmp = getDBFilename<RIGHT_SIDE>(baseMapping, true);
-
- const Zstring dbNameLeft = getDBFilename<LEFT_SIDE >(baseMapping);
- const Zstring dbNameRight = getDBFilename<RIGHT_SIDE>(baseMapping);
-
- //delete old tmp file, if necessary -> throws if deletion fails!
- removeFile(dbNameLeftTmp); //
- removeFile(dbNameRightTmp); //throw FileError
-
- //(try to) load old database files...
- StreamMapping streamListLeft;
- StreamMapping streamListRight;
-
- try //read file data: list of session ID + DirInfo-stream
- {
- streamListLeft = ::loadStreams(dbNameLeft);
- }
- catch (FileError&) {} //if error occurs: just overwrite old file! User is already informed about issues right after comparing!
- try
- {
- streamListRight = ::loadStreams(dbNameRight);
- }
- catch (FileError&) {}
-
- //find associated session: there can be at most one session within intersection of left and right ids
- StreamMapping::iterator streamLeft = streamListLeft .end();
- StreamMapping::iterator streamRight = streamListRight.end();
- for (auto iterLeft = streamListLeft.begin(); iterLeft != streamListLeft.end(); ++iterLeft)
- {
- auto iterRight = streamListRight.find(iterLeft->first);
- if (iterRight != streamListRight.end())
- {
- streamLeft = iterLeft;
- streamRight = iterRight;
- break;
- }
- }
-
- //(try to) read old DirInfo
- DirInfoPtr oldDirInfoLeft;
- DirInfoPtr oldDirInfoRight;
- try
- {
- if (streamLeft != streamListLeft .end() &&
- streamRight != streamListRight.end() &&
- streamLeft ->second.get() &&
- streamRight->second.get())
- {
- oldDirInfoLeft = parseStream(*streamLeft ->second, dbNameLeft); //throw FileError
- oldDirInfoRight = parseStream(*streamRight->second, dbNameRight); //throw FileError
- }
- }
- catch (FileError&)
- {
- //if error occurs: just overwrite old file! User is already informed about issues right after comparing!
- oldDirInfoLeft .reset(); //read both or none!
- oldDirInfoRight.reset(); //
- }
-
- //create new database entries
- MemoryStreamPtr newStreamLeft = std::make_shared<std::vector<char>>();
- {
- wxMemoryOutputStream buffer;
- const DirContainer* oldDir = oldDirInfoLeft.get() ? &oldDirInfoLeft->baseDirContainer : NULL;
- SaveDirInfo<LEFT_SIDE>(baseMapping, oldDir, toWx(dbNameLeft), buffer);
- newStreamLeft->resize(buffer.GetSize()); //convert output stream to char-array
- buffer.CopyTo(&(*newStreamLeft)[0], buffer.GetSize()); //
- }
-
- MemoryStreamPtr newStreamRight = std::make_shared<std::vector<char>>();
- {
- wxMemoryOutputStream buffer;
- const DirContainer* oldDir = oldDirInfoRight.get() ? &oldDirInfoRight->baseDirContainer : NULL;
- SaveDirInfo<RIGHT_SIDE>(baseMapping, oldDir, toWx(dbNameRight), buffer);
- newStreamRight->resize(buffer.GetSize()); //convert output stream to char-array
- buffer.CopyTo(&(*newStreamRight)[0], buffer.GetSize()); //
- }
-
- //check if there is some work to do at all
- {
- const bool updateRequiredLeft = streamLeft == streamListLeft .end() || !equalEntry(newStreamLeft, streamLeft ->second);
- const bool updateRequiredRight = streamRight == streamListRight.end() || !equalEntry(newStreamRight, streamRight->second);
- //some users monitor the *.ffs_db file with RTS => don't touch the file if it isnt't strictly needed
- if (!updateRequiredLeft && !updateRequiredRight)
- return;
- }
-
- //create/update DirInfo-streams
- std::string sessionID = util::generateGUID();
-
- //erase old session data
- if (streamLeft != streamListLeft.end())
- streamListLeft.erase(streamLeft);
- if (streamRight != streamListRight.end())
- streamListRight.erase(streamRight);
-
- //fill in new
- streamListLeft .insert(std::make_pair(sessionID, newStreamLeft));
- streamListRight.insert(std::make_pair(sessionID, newStreamRight));
-
- //write (temp-) files...
- Loki::ScopeGuard guardTempFileLeft = Loki::MakeGuard(&zen::removeFile, dbNameLeftTmp);
- saveFile(streamListLeft, dbNameLeftTmp); //throw FileError
-
- Loki::ScopeGuard guardTempFileRight = Loki::MakeGuard(&zen::removeFile, dbNameRightTmp);
- saveFile(streamListRight, dbNameRightTmp); //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(dbNameLeft);
- removeFile(dbNameRight);
- renameFile(dbNameLeftTmp, dbNameLeft); //throw FileError;
- renameFile(dbNameRightTmp, dbNameRight); //throw FileError;
-
- guardTempFileLeft. Dismiss(); //no need to delete temp file anymore
- guardTempFileRight.Dismiss(); //
-}
bgstack15