summaryrefslogtreecommitdiff
path: root/FreeFileSync/Source/base/dir_exist_async.h
blob: a344cc06e5edbc9752b698ef562a0fb4e3ab2f21 (plain)
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
155
156
157
// *****************************************************************************
// * 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 DIR_EXIST_ASYNC_H_0817328167343215806734213
#define DIR_EXIST_ASYNC_H_0817328167343215806734213

#include <zen/thread.h>
#include "process_callback.h"
#include "../afs/abstract.h"


namespace fff
{
namespace
{
struct FolderStatus
{
    std::set<AbstractPath> existing;
    std::set<AbstractPath> notExisting;
    std::map<AbstractPath, zen::FileError> failedChecks;
};
//- directory existence checking may hang for non-existent network drives => run asynchronously and update UI!
//- check existence of all directories in parallel! (avoid adding up search times if multiple network drives are not reachable)
//- authenticateAccess() better be integrated into folder existence check: if fails, why bother to go on with the folder!?
//- probably don't need timeout: https://freefilesync.org/forum/viewtopic.php?t=7350#p36817
//   => benefit: waits until user login completed in AFS::authenticateAccess()
FolderStatus getFolderStatusParallel(const std::set<AbstractPath>& folderPaths,
                                     bool authenticateAccess, const AFS::RequestPasswordFun& requestPassword /*throw X*/,
                                     PhaseCallback& cb /*throw X*/) //throw X
{
    using namespace zen;

    //aggregate folder paths that are on the same root device: see parallel_scan.h
    std::map<AfsDevice, std::set<AbstractPath>> perDevicePaths;

    for (const AbstractPath& folderPath : folderPaths)
        if (!AFS::isNullPath(folderPath)) //skip empty folders
            perDevicePaths[folderPath.afsDevice].insert(folderPath);
    //----------------------------------------------------------------------

    std::vector<std::pair<AbstractPath, std::future<bool>>> futFoldersExist;

    struct AsyncPrompt
    {
        std::wstring msg;
        std::wstring lastErrorMsg;
        std::promise<Zstring> promPassword;
    };
    auto protPromptsPending = authenticateAccess && requestPassword ? std::make_shared<Protected<RingBuffer<AsyncPrompt>>>() : nullptr;

    //----------------------------------------------------------------------
    std::vector<ThreadGroup<std::packaged_task<bool()>>> deviceThreadGroups;
    for (const auto& [device, deviceFolderPaths] : perDevicePaths)
    {
        deviceThreadGroups.emplace_back(1,           Zstr("DirExist: ") + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(device, AfsPath()))));
        deviceThreadGroups.back().detach(); //don't wait on hanging threads if user cancels

        //1. login to network share, connect with Google Drive, etc.
        std::shared_future<void> futAuth;
        if (authenticateAccess)
        {
            AFS::RequestPasswordFun threadRequestPassword; //throw std::future_error
            if (requestPassword)
                threadRequestPassword = [promptsPendingWeak = std::weak_ptr(protPromptsPending)](const std::wstring& msg, const std::wstring& lastErrorMsg)
            {
                std::future<Zstring> futPassword;
                if (auto protPromptsPending2 = promptsPendingWeak.lock()) //[!] not owned by worker thread!
                    protPromptsPending2->access([&](RingBuffer<AsyncPrompt>& promptsPending)
                {
                    promptsPending.push_back(AsyncPrompt{msg, lastErrorMsg, {}});
                    futPassword = promptsPending.back().promPassword.get_future();
                });
                return futPassword.get(); //throw std::future_error -> if std::promise<Zstring> destroyed before password was set
            };

            futAuth = runAsync([device /*clang bug*/= device, threadRequestPassword]
            {
                setCurrentThreadName(Zstr("Auth: ") + utfTo<Zstring>(AFS::getDisplayPath(AbstractPath(device, AfsPath()))));
                AFS::authenticateAccess(device, threadRequestPassword); //throw FileError, std::future_error
            });
        }

        for (const AbstractPath& folderPath : deviceFolderPaths)
        {
            std::packaged_task<bool()> pt([folderPath, futAuth]
            {
                if (futAuth.valid())
                    futAuth.get(); //throw FileError, std::future_error

                /* 2. check dir existence:

                   CAVEAT: the case-sensitive semantics of AFS::itemExists() do not fit here!
                        BUT: its implementation happens to be okay for our use:
                    Assume we have a case-insensitive path match:
                    => AFS::itemExists() first checks AFS::getItemType()
                    => either succeeds (fine) or fails because of 1. not existing or 2. access error
                    => if the subsequent case-sensitive folder search also doesn't find the folder: only a problem in case 2
                    => FFS tries to create the folder during sync and fails with I. access error (fine) or II. already existing (obscures the previous "access error") */
                return AFS::itemExists(folderPath); //throw FileError; return "false" IFF nothing (of any type) exists

                //check for ItemType::file? too pedantic?
                //    throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getDisplayPath(folderPath))),
                //                    replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(folderPath))));
            });
            auto futIsExisting = pt.get_future();
            deviceThreadGroups.back().run(std::move(pt));

            futFoldersExist.emplace_back(folderPath, std::move(futIsExisting));
        }
    }
    //----------------------------------------------------------------------

    FolderStatus output;

    for (auto& [folderPath, futFolderExists] : futFoldersExist)
    {
        cb.updateStatus(replaceCpy(_("Searching for folder %x..."), L"%x", fmtPath(AFS::getDisplayPath(folderPath)))); //throw X

        while (futFolderExists.wait_for(UI_UPDATE_INTERVAL / 2) == std::future_status::timeout)
        {
            cb.requestUiUpdate(); //throw X

            //marshal password prompt callback from current thread (probably main) to worker threads
            //=> polling delay doesn't matter because user interaction is required
            if (protPromptsPending)
                protPromptsPending->access([&](RingBuffer<AsyncPrompt>& promptsPending)
            {
                //do work while holding Protected<> lock!? device authentication threads blocking doesn't matter because prompts are serialized to GUI anyway
                if (!promptsPending.empty())
                {
                    assert(requestPassword); //... in this context
                    const Zstring password = requestPassword(promptsPending.front().msg, promptsPending.front().lastErrorMsg); //throw X
                    promptsPending.front().promPassword.set_value(password);
                    promptsPending.pop_front();
                }
            });
        }

        try
        {
            //call future::get() only *once*! otherwise: undefined behavior!
            if (futFolderExists.get()) //throw FileError, (std::future_error)
                output.existing.insert(folderPath);
            else
                output.notExisting.insert(folderPath);
        }
        catch (const FileError& e) { output.failedChecks.emplace(folderPath, e); }
    }
    return output;
}
}
}

#endif //DIR_EXIST_ASYNC_H_0817328167343215806734213
bgstack15