summaryrefslogtreecommitdiff
path: root/FreeFileSync/Source/base/dir_exist_async.h
blob: ea524d88d42fa0c478f3f9a4a8adf545ca5387ad (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
// *****************************************************************************
// * 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 <zen/file_error.h>
#include <zen/basic_math.h>
#include "process_callback.h"
#include "../fs/abstract.h"


namespace fff
{
const int DEFAULT_FOLDER_ACCESS_TIME_OUT_SEC = 20; //consider CD-ROM insert or hard disk spin up time from sleep

namespace
{
//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)
//- add reasonable time-out time!
//- avoid checking duplicate entries => std::set
struct FolderStatus
{
    std::set<AbstractPath> existing;
    std::set<AbstractPath> notExisting;
    std::map<AbstractPath, zen::FileError> failedChecks;

    std::map<AbstractPath, AbstractPath> normalizedPathsEx; //get rid of folder aliases (e.g. path differing in case)
};

AbstractPath getNormalizedPath(const FolderStatus& status, const AbstractPath& folderPath)
{
    auto it = status.normalizedPathsEx.find(folderPath);
    return it == status.normalizedPathsEx.end() ? folderPath : it->second;
}


FolderStatus getFolderStatusNonBlocking(const std::set<AbstractPath>& folderPaths, const std::map<AfsDevice, size_t>& deviceParallelOps,
                                        bool allowUserInteraction, ProcessCallback& procCallback  /*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<std::optional<AFS::FileId>>>> futureDetails;

    std::vector<ThreadGroup<std::packaged_task<std::optional<AFS::FileId>()>>> perDeviceThreads;
    for (const auto& [afsDevice, deviceFolderPaths] : perDevicePaths)
    {
        const size_t parallelOps = getDeviceParallelOps(deviceParallelOps, afsDevice);

        perDeviceThreads.emplace_back(parallelOps, "DirExist: " + utfTo<std::string>(AFS::getDisplayPath(AbstractPath(afsDevice, AfsPath()))));
        auto& threadGroup = perDeviceThreads.back();
        threadGroup.detach(); //don't wait on threads hanging longer than "folderAccessTimeout"

        for (const AbstractPath& folderPath : deviceFolderPaths)
        {
            std::packaged_task<std::optional<AFS::FileId>()> pt([folderPath, allowUserInteraction]() -> std::optional<AFS::FileId>
            {
                //1. login to network share, open FTP connection, etc.
                AFS::connectNetworkFolder(folderPath, allowUserInteraction); //throw FileError

                //2. check dir existence (...by doing something useful and getting the file ID)
                std::exception_ptr fidError;
                try
                {
                    const AFS::FileId fileId = AFS::getFileId(folderPath); //throw FileError
                    if (!fileId.empty()) //=> folder exists
                        return fileId;
                }
                catch (FileError&) { fidError = std::current_exception(); }
                //else: error or fileId not available, e.g. FTP, SFTP

                /* CAVEAT: the case-sensitive semantics of AFS::itemStillExists() do not fit here!
                        BUT: its implementation happens to be okay for our use:
                    Assume we have a case-insensitive path match:
                    => AFS::itemStillExists() first checks AFS::getItemType()
                    => either succeeds (fine) or fails because of 1. not existing or 2. access error
                    => the subsequent folder search reports "no 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") */
                if (!AFS::itemStillExists(folderPath)) //throw FileError
                    return {};

                if (fidError)
                    std::rethrow_exception(fidError);
                else
                    return AFS::FileId();
                //consider ItemType::FILE a failure instead? Meanwhile: return "false" IFF nothing (of any type) exists
            });
            auto fut = pt.get_future();
            threadGroup.run(std::move(pt));

            futureDetails.emplace_back(folderPath, std::move(fut));
        }
    }

    //don't wait (almost) endlessly like Win32 would on non-existing network shares:
    const auto startTime = std::chrono::steady_clock::now();

    FolderStatus output;
    std::map<std::pair<AfsDevice, AFS::FileId>, AbstractPath> exFoldersById; //volume serial is NOT always globally unique!
    //=> combine with AfsDevice https://freefilesync.org/forum/viewtopic.php?t=5815

    for (auto& [folderPath, future] : futureDetails)
    {
        const std::wstring& displayPathFmt = fmtPath(AFS::getDisplayPath(folderPath));

        procCallback.reportStatus(replaceCpy(_("Searching for folder %x..."), L"%x", displayPathFmt)); //throw X

        const int deviceTimeOut = AFS::geAccessTimeout(folderPath); //0 if no timeout in force
        const auto timeoutTime = startTime + std::chrono::seconds(deviceTimeOut > 0 ? deviceTimeOut : DEFAULT_FOLDER_ACCESS_TIME_OUT_SEC);

        while (std::chrono::steady_clock::now() < timeoutTime &&
               future.wait_for(UI_UPDATE_INTERVAL / 2) != std::future_status::ready)
            procCallback.requestUiRefresh(); //throw X

        if (!isReady(future))
            output.failedChecks.emplace(folderPath, FileError(replaceCpy(_("Timeout while searching for folder %x."), L"%x", displayPathFmt)));
        else
            try
            {
                //call future::get() only *once*! otherwise: undefined behavior!
                if (std::optional<AFS::FileId> folderInfo = future.get()) //throw FileError
                {
                    output.existing.emplace(folderPath);

                    //find folder aliases (e.g. path differing in case)
                    const AFS::FileId fileId = *folderInfo;
                    if (!fileId.empty())
                        exFoldersById.emplace(std::pair(folderPath.afsDevice, fileId), folderPath);

                    output.normalizedPathsEx.emplace(folderPath, fileId.empty() ? folderPath : exFoldersById.find(std::pair(folderPath.afsDevice, fileId))->second);
                }
                else
                    output.notExisting.insert(folderPath);
            }
            catch (const FileError& e) { output.failedChecks.emplace(folderPath, e); }
    }
    return output;
}
}
}

#endif //DIR_EXIST_ASYNC_H_0817328167343215806734213
bgstack15