File: abstract_impl.h

package info (click to toggle)
freefilesync 13.7-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,044 kB
  • sloc: cpp: 66,712; ansic: 447; makefile: 216
file content (154 lines) | stat: -rw-r--r-- 5,627 bytes parent folder | download | duplicates (3)
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