summaryrefslogtreecommitdiff
path: root/lib/versioning.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/versioning.cpp')
-rw-r--r--lib/versioning.cpp439
1 files changed, 0 insertions, 439 deletions
diff --git a/lib/versioning.cpp b/lib/versioning.cpp
deleted file mode 100644
index 82696e3a..00000000
--- a/lib/versioning.cpp
+++ /dev/null
@@ -1,439 +0,0 @@
-#include "versioning.h"
-#include <map>
-#include <zen/file_handling.h>
-#include <zen/file_traverser.h>
-#include <zen/string_tools.h>
-
-using namespace zen;
-
-
-namespace
-{
-Zstring getExtension(const Zstring& relativeName) //including "." if extension is existing, returns empty string otherwise
-{
- auto iterSep = find_last(relativeName.begin(), relativeName.end(), FILE_NAME_SEPARATOR);
- auto iterName = iterSep != relativeName.end() ? iterSep + 1 : relativeName.begin(); //find beginning of short name
- auto iterDot = find_last(iterName, relativeName.end(), Zstr('.')); //equal to relativeName.end() if file has no extension!!
- return Zstring(&*iterDot, relativeName.end() - iterDot);
-};
-}
-
-bool impl::isMatchingVersion(const Zstring& shortname, const Zstring& shortnameVersion) //e.g. ("Sample.txt", "Sample.txt 2012-05-15 131513.txt")
-{
- auto it = shortnameVersion.begin();
- auto last = shortnameVersion.end();
-
- auto nextDigit = [&]() -> bool
- {
- if (it == last || !isDigit(*it))
- return false;
- ++it;
- return true;
- };
- auto nextDigits = [&](size_t count) -> bool
- {
- while (count-- > 0)
- if (!nextDigit())
- return false;
- return true;
- };
- auto nextChar = [&](Zchar c) -> bool
- {
- if (it == last || *it != c)
- return false;
- ++it;
- return true;
- };
- auto nextStringI = [&](const Zstring& str) -> bool //windows: ignore case!
- {
- if (last - it < static_cast<ptrdiff_t>(str.size()) || !EqualFilename()(str, Zstring(&*it, str.size())))
- return false;
- it += str.size();
- return true;
- };
-
- return nextStringI(shortname) && //versioned file starts with original name
- nextChar(Zstr(' ')) && //validate timestamp: e.g. "2012-05-15 131513"; Regex: \d{4}-\d{2}-\d{2} \d{6}
- nextDigits(4) && //YYYY
- nextChar(Zstr('-')) && //
- nextDigits(2) && //MM
- nextChar(Zstr('-')) && //
- nextDigits(2) && //DD
- nextChar(Zstr(' ')) && //
- nextDigits(6) && //HHMMSS
- nextStringI(getExtension(shortname)) &&
- it == last;
-}
-
-
-namespace
-{
-/*
-- handle not existing source
-- create target super directories if missing
-*/
-template <class Function>
-void moveItemToVersioning(const Zstring& fullName, //throw FileError
- const Zstring& relativeName,
- const Zstring& versioningDirectory,
- const Zstring& timestamp,
- VersioningStyle versioningStyle,
- Function moveObj) //move source -> target; may throw FileError
-{
- assert(!startsWith(relativeName, FILE_NAME_SEPARATOR));
- assert(!endsWith (relativeName, FILE_NAME_SEPARATOR));
-
- Zstring targetName;
- switch (versioningStyle)
- {
- case VER_STYLE_REPLACE:
- targetName = appendSeparator(versioningDirectory) + relativeName;
- break;
-
- case VER_STYLE_ADD_TIMESTAMP:
- //assemble time-stamped version name
- targetName = appendSeparator(versioningDirectory) + relativeName + Zstr(' ') + timestamp + getExtension(relativeName);
- assert(impl::isMatchingVersion(afterLast(relativeName, FILE_NAME_SEPARATOR), afterLast(targetName, FILE_NAME_SEPARATOR))); //paranoid? no!
- break;
- }
-
- try
- {
- moveObj(fullName, targetName); //throw FileError
- }
- catch (FileError&) //expected to fail if target directory is not yet existing!
- {
- if (!somethingExists(fullName)) //no source at all is not an error (however a directory as source when a file is expected, *is* an error!)
- return; //object *not* processed
-
- //create intermediate directories if missing
- const Zstring targetDir = beforeLast(targetName, FILE_NAME_SEPARATOR);
- if (!dirExists(targetDir)) //->(minor) file system race condition!
- {
- makeDirectory(targetDir); //throw FileError
- moveObj(fullName, targetName); //throw FileError -> this should work now!
- }
- else
- throw;
- }
-}
-
-
-//move source to target across volumes
-//no need to check if: - super-directories of target exist - source exists: done by moveItemToVersioning()
-//if target already exists, it is overwritten, even if it is a different type, e.g. a directory!
-template <class Function>
-void moveObject(const Zstring& sourceFile, //throw FileError
- const Zstring& targetFile,
- Function copyDelete) //throw FileError; fallback if move failed
-{
- assert(fileExists(sourceFile) || symlinkExists(sourceFile) || !somethingExists(sourceFile)); //we process files and symlinks only
-
- auto removeTarget = [&]
- {
- //remove target object
- if (dirExists(targetFile)) //directory or dir-symlink
- removeDirectory(targetFile); //throw FileError; we do not expect targetFile to be a directory in general => no callback required
- else //file or (broken) file-symlink
- removeFile(targetFile); //throw FileError
- };
-
- //first try to move directly without copying
- try
- {
- renameFile(sourceFile, targetFile); //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 file)
- catch (const ErrorDifferentVolume&)
- {
- removeTarget(); //throw FileError
- copyDelete(); //
- }
- catch (const ErrorTargetExisting&)
- {
- removeTarget(); //throw FileError
- try
- {
- renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- }
- catch (const ErrorDifferentVolume&)
- {
- copyDelete(); //throw FileError
- }
- }
-}
-
-
-void moveFile(const Zstring& sourceFile, const Zstring& targetFile, CallbackCopyFile& callback) //throw FileError
-{
- moveObject(sourceFile, //throw FileError
- targetFile,
- [&]
- {
- //create target
- if (symlinkExists(sourceFile))
- copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions
- else
- copyFile(sourceFile, targetFile, false, true, &callback); //throw FileError - permissions "false", transactional copy "true"
-
- //delete source
- removeFile(sourceFile); //throw FileError; newly copied file is NOT deleted if exception is thrown here!
- });
-}
-
-
-void moveDirSymlink(const Zstring& sourceLink, const Zstring& targetLink) //throw FileError
-{
- moveObject(sourceLink, //throw FileError
- targetLink,
- [&]
- {
- //create target
- copySymlink(sourceLink, targetLink, false); //throw FileError; don't copy filesystem permissions
-
- //delete source
- removeDirectory(sourceLink); //throw FileError; newly copied link is NOT deleted if exception is thrown here!
- });
-}
-
-
-class TraverseFilesOneLevel : public TraverseCallback
-{
-public:
- TraverseFilesOneLevel(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : files_(files), dirs_(dirs) {}
-
-private:
- virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
- {
- files_.push_back(shortName);
- }
-
- virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
- {
- if (dirExists(fullName)) //dir symlink
- dirs_.push_back(shortName);
- else //file symlink, broken symlink
- files_.push_back(shortName);
- return LINK_SKIP;
- }
-
- virtual TraverseCallback* onDir(const Zchar* shortName, const Zstring& fullName)
- {
- dirs_.push_back(shortName);
- return nullptr; //DON'T traverse into subdirs; moveDirectory works recursively!
- }
-
- virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) { throw FileError(msg); }
- virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) { throw FileError(msg); }
-
- std::vector<Zstring>& files_;
- std::vector<Zstring>& dirs_;
-};
-}
-
-
-bool FileVersioner::revisionFile(const Zstring& fullName, const Zstring& relativeName, CallbackMoveFile& callback) //throw FileError
-{
- struct CallbackMoveFileImpl : public CallbackMoveDir
- {
- CallbackMoveFileImpl(CallbackMoveFile& callback) : callback_(callback) {}
- private:
- virtual void onBeforeFileMove(const Zstring& fileFrom, const Zstring& fileTo) {}
- virtual void onBeforeDirMove (const Zstring& dirFrom, const Zstring& dirTo ) {}
- virtual void updateStatus(Int64 bytesDelta) { callback_.updateStatus(bytesDelta); }
- CallbackMoveFile& callback_;
- } cb(callback);
-
- return revisionFileImpl(fullName, relativeName, cb); //throw FileError
-}
-
-
-bool FileVersioner::revisionFileImpl(const Zstring& fullName, const Zstring& relativeName, CallbackMoveDir& callback) //throw FileError
-{
- bool moveSuccessful = false;
-
- moveItemToVersioning(fullName, //throw FileError
- relativeName,
- versioningDirectory_,
- timeStamp_,
- versioningStyle_,
- [&](const Zstring& source, const Zstring& target)
- {
- callback.onBeforeFileMove(source, target); //if we're called by revisionDirImpl() we know that "source" exists!
- //when called by revisionFile(), "source" might not exist, however onBeforeFileMove() is not propagated in this case!
-
- struct CopyCallbackImpl : public CallbackCopyFile
- {
- CopyCallbackImpl(CallbackMoveDir& callback) : callback_(callback) {}
- private:
- virtual void deleteTargetFile(const Zstring& targetFile) { assert(!somethingExists(targetFile)); }
- virtual void updateCopyStatus(Int64 bytesDelta) { callback_.updateStatus(bytesDelta); }
- CallbackMoveDir& callback_;
- } copyCallback(callback);
-
- moveFile(source, target, copyCallback); //throw FileError
- moveSuccessful = true;
- });
- return moveSuccessful;
-}
-
-
-void FileVersioner::revisionDir(const Zstring& fullName, const Zstring& relativeName, CallbackMoveDir& callback) //throw FileError
-{
- //no error situation if directory is not existing! manual deletion relies on it!
- if (!somethingExists(fullName))
- return; //neither directory nor any other object (e.g. broken symlink) with that name existing
- revisionDirImpl(fullName, relativeName, callback); //throw FileError
-}
-
-
-void FileVersioner::revisionDirImpl(const Zstring& fullName, const Zstring& relativeName, CallbackMoveDir& callback) //throw FileError
-{
- assert(somethingExists(fullName)); //[!]
-
- //create target
- if (symlinkExists(fullName)) //on Linux there is just one type of symlink, and since we do revision file symlinks, we should revision dir symlinks as well!
- {
- moveItemToVersioning(fullName, //throw FileError
- relativeName,
- versioningDirectory_,
- timeStamp_,
- versioningStyle_,
- [&](const Zstring& source, const Zstring& target)
- {
- callback.onBeforeDirMove(source, target);
- moveDirSymlink(source, target); //throw FileError
- });
- }
- else
- {
- assert(!startsWith(relativeName, FILE_NAME_SEPARATOR));
- assert(endsWith(fullName, relativeName)); //usually, yes, but we might relax this in the future
- const Zstring targetDir = appendSeparator(versioningDirectory_) + relativeName;
-
- //makeDirectory(targetDir); //FileError -> create only when needed in moveFileToVersioning(); avoids empty directories
-
- //traverse source directory one level
- std::vector<Zstring> fileList; //list of *short* names
- std::vector<Zstring> dirList; //
- {
- TraverseFilesOneLevel tol(fileList, dirList); //throw FileError
- traverseFolder(fullName, tol); //
- }
-
- const Zstring fullNamePf = appendSeparator(fullName);
- const Zstring relnamePf = appendSeparator(relativeName);
-
- //move files
- std::for_each(fileList.begin(), fileList.end(),
- [&](const Zstring& shortname)
- {
- revisionFileImpl(fullNamePf + shortname, //throw FileError
- relnamePf + shortname,
- callback);
- });
-
- //move items in subdirectories
- std::for_each(dirList.begin(), dirList.end(),
- [&](const Zstring& shortname)
- {
- revisionDirImpl(fullNamePf + shortname, //throw FileError
- relnamePf + shortname,
- callback);
- });
-
- //delete source
- callback.onBeforeDirMove(fullName, targetDir);
- removeDirectory(fullName); //throw FileError
- }
-}
-
-
-/*
-namespace
-{
-class TraverseVersionsOneLevel : public TraverseCallback
-{
-public:
- TraverseVersionsOneLevel(std::vector<Zstring>& files, std::function<void()> updateUI) : files_(files), updateUI_(updateUI) {}
-
-private:
- virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) { files_.push_back(shortName); updateUI_(); }
- virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { files_.push_back(shortName); updateUI_(); return LINK_SKIP; }
- virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName) { updateUI_(); return nullptr; } //DON'T traverse into subdirs
- virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); }
- virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); }
-
- std::vector<Zstring>& files_;
- std::function<void()> updateUI_;
-};
-}
-
-void FileVersioner::limitVersions(std::function<void()> updateUI) //throw FileError
-{
- if (versionCountLimit_ < 0) //no limit!
- return;
-
- //buffer map "directory |-> list of immediate child file and symlink short names"
- std::map<Zstring, std::vector<Zstring>, LessFilename> dirBuffer;
-
- auto getVersionsBuffered = [&](const Zstring& dirname) -> const std::vector<Zstring>&
- {
- auto it = dirBuffer.find(dirname);
- if (it != dirBuffer.end())
- return it->second;
-
- std::vector<Zstring> fileShortNames;
- TraverseVersionsOneLevel tol(fileShortNames, updateUI); //throw FileError
- traverseFolder(dirname, tol);
-
- auto& newEntry = dirBuffer[dirname]; //transactional behavior!!!
- newEntry.swap(fileShortNames); //-> until C++11 emplace is available
-
- return newEntry;
- };
-
- std::for_each(fileRelNames.begin(), fileRelNames.end(),
- [&](const Zstring& relativeName) //e.g. "subdir\Sample.txt"
- {
- const Zstring fullname = appendSeparator(versioningDirectory_) + relativeName; //e.g. "D:\Revisions\subdir\Sample.txt"
- const Zstring parentDir = beforeLast(fullname, FILE_NAME_SEPARATOR); //e.g. "D:\Revisions\subdir"
- const Zstring shortname = afterLast(relativeName, FILE_NAME_SEPARATOR); //e.g. "Sample.txt"; returns the whole string if seperator not found
-
- const std::vector<Zstring>& allVersions = getVersionsBuffered(parentDir);
-
- //filter out only those versions that match the given relative name
- std::vector<Zstring> matches; //e.g. "Sample.txt 2012-05-15 131513.txt"
-
- std::copy_if(allVersions.begin(), allVersions.end(), std::back_inserter(matches),
- [&](const Zstring& shortnameVer) { return impl::isMatchingVersion(shortname, shortnameVer); });
-
- //take advantage of version naming convention to find oldest versions
- if (matches.size() <= static_cast<size_t>(versionCountLimit_))
- return;
- std::nth_element(matches.begin(), matches.end() - versionCountLimit_, matches.end(), LessFilename()); //windows: ignore case!
-
- //delete obsolete versions
- std::for_each(matches.begin(), matches.end() - versionCountLimit_,
- [&](const Zstring& shortnameVer)
- {
- updateUI();
- const Zstring fullnameVer = parentDir + FILE_NAME_SEPARATOR + shortnameVer;
- try
- {
- removeFile(fullnameVer); //throw FileError
- }
- catch (FileError&)
- {
-#ifdef ZEN_WIN //if it's a directory symlink:
- if (symlinkExists(fullnameVer) && dirExists(fullnameVer))
- removeDirectory(fullnameVer); //throw FileError
- else
-#endif
- throw;
- }
- });
- });
-}
-*/
bgstack15