1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// *****************************************************************************
// * 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.lock(); });
//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
|