155 lines
5.5 KiB
C++
155 lines
5.5 KiB
C++
// *****************************************************************************
|
|
// * This file is part of the FreeFileSync project. It is distributed under *
|
|
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
|
|
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
|
|
// *****************************************************************************
|
|
|
|
#ifndef IMPL_HELPER_H_873450978453042524534234
|
|
#define IMPL_HELPER_H_873450978453042524534234
|
|
|
|
#include "abstract.h"
|
|
#include <zen/thread.h>
|
|
#include <zen/stream_buffer.h>
|
|
|
|
|
|
namespace fff
|
|
{
|
|
template <class Function> inline //return ignored error message if available
|
|
std::wstring tryReportingDirError(Function cmd /*throw FileError, X*/, AbstractFileSystem::TraverserCallback& cb /*throw X*/)
|
|
{
|
|
for (size_t retryNumber = 0;; ++retryNumber)
|
|
try
|
|
{
|
|
cmd(); //throw FileError, X
|
|
return std::wstring();
|
|
}
|
|
catch (const zen::FileError& e)
|
|
{
|
|
assert(!e.toString().empty());
|
|
switch (cb.reportDirError({e.toString(), std::chrono::steady_clock::now(), retryNumber})) //throw X
|
|
{
|
|
case AbstractFileSystem::TraverserCallback::HandleError::ignore:
|
|
return e.toString();
|
|
case AbstractFileSystem::TraverserCallback::HandleError::retry:
|
|
break; //continue with loop
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class Command> inline
|
|
bool tryReportingItemError(Command cmd, AbstractFileSystem::TraverserCallback& callback, const Zstring& itemName) //throw X, return "true" on success, "false" if error was ignored
|
|
{
|
|
for (size_t retryNumber = 0;; ++retryNumber)
|
|
try
|
|
{
|
|
cmd(); //throw FileError
|
|
return true;
|
|
}
|
|
catch (const zen::FileError& e)
|
|
{
|
|
switch (callback.reportItemError({e.toString(), std::chrono::steady_clock::now(), retryNumber}, itemName)) //throw X
|
|
{
|
|
case AbstractFileSystem::TraverserCallback::HandleError::retry:
|
|
break;
|
|
case AbstractFileSystem::TraverserCallback::HandleError::ignore:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================================
|
|
|
|
//Google Drive/MTP happily create duplicate files/folders with the same names, without failing
|
|
//=> however, FFS's "check if already exists after failure" idiom *requires* failure
|
|
//=> best effort: serialize access (at path level) so that GdriveFileState existence check and file/folder creation act as a single operation
|
|
template <class NativePath>
|
|
class PathAccessLocker
|
|
{
|
|
struct BlockInfo
|
|
{
|
|
std::mutex m;
|
|
bool itemInUse = false; //protected by mutex!
|
|
/* can we get rid of BlockType::fail and save "bool itemInUse" "somewhere else"?
|
|
Google Drive => put dummy entry in GdriveFileState? problem: there is no fail-free removal: accessGlobalFileState() can throw!
|
|
MTP => no (buffered) state */
|
|
};
|
|
public:
|
|
PathAccessLocker() {}
|
|
|
|
//how to handle *other* access attempts while holding the lock:
|
|
enum class BlockType
|
|
{
|
|
otherWait,
|
|
otherFail
|
|
};
|
|
|
|
class Lock
|
|
{
|
|
public:
|
|
Lock(const NativePath& nativePath, BlockType blockType) : blockType_(blockType) //throw SysError
|
|
{
|
|
using namespace zen;
|
|
|
|
if (const std::shared_ptr<PathAccessLocker> pal = getGlobalInstance())
|
|
pal->protPathLocks_.access([&](std::map<NativePath, std::weak_ptr<BlockInfo>>& pathLocks)
|
|
{
|
|
//clean up obsolete entries
|
|
std::erase_if(pathLocks, [](const auto& v) { return v.second.expired(); });
|
|
|
|
//get or create:
|
|
std::weak_ptr<BlockInfo>& weakPtr = pathLocks[nativePath];
|
|
blockInfo_ = weakPtr.lock();
|
|
if (!blockInfo_)
|
|
weakPtr = blockInfo_ = std::make_shared<BlockInfo>();
|
|
});
|
|
else
|
|
throw SysError(L"PathAccessLocker::Lock() function call not allowed during init/shutdown.");
|
|
|
|
blockInfo_->m.lock();
|
|
|
|
if (blockInfo_->itemInUse)
|
|
{
|
|
blockInfo_->m.unlock();
|
|
throw SysError(replaceCpy(_("The item %x is currently in use."), L"%x", fmtPath(getItemName(nativePath))));
|
|
}
|
|
|
|
if (blockType == BlockType::otherFail)
|
|
{
|
|
blockInfo_->itemInUse = true;
|
|
blockInfo_->m.unlock();
|
|
}
|
|
}
|
|
|
|
~Lock()
|
|
{
|
|
if (blockType_ == BlockType::otherFail)
|
|
{
|
|
blockInfo_->m.lock();
|
|
blockInfo_->itemInUse = false;
|
|
}
|
|
|
|
blockInfo_->m.unlock();
|
|
}
|
|
|
|
private:
|
|
Lock (const Lock&) = delete;
|
|
Lock& operator=(const Lock&) = delete;
|
|
|
|
const BlockType blockType_; //[!] needed: we can't instead check "itemInUse" (without locking first)
|
|
std::shared_ptr<BlockInfo> blockInfo_;
|
|
};
|
|
|
|
private:
|
|
PathAccessLocker (const PathAccessLocker&) = delete;
|
|
PathAccessLocker& operator=(const PathAccessLocker&) = delete;
|
|
|
|
static std::shared_ptr<PathAccessLocker> getGlobalInstance();
|
|
static Zstring getItemName(const NativePath& nativePath);
|
|
|
|
zen::Protected<std::map<NativePath, std::weak_ptr<BlockInfo>>> protPathLocks_;
|
|
};
|
|
|
|
}
|
|
|
|
#endif //IMPL_HELPER_H_873450978453042524534234
|