summaryrefslogtreecommitdiff
path: root/FreeFileSync/Source/status_handler.h
blob: 9058033b82060acf991c2bef9663298de1c29898 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// *****************************************************************************
// * 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 STATUS_HANDLER_H_81704805908341534
#define STATUS_HANDLER_H_81704805908341534

#include <vector>
#include <chrono>
#include <thread>
#include <string>
#include <zen/i18n.h>
#include <zen/basic_math.h>
#include "base/process_callback.h"
#include "return_codes.h"


namespace fff
{
bool uiUpdateDue(bool force = false); //test if a specific amount of time is over

/*
Updating GUI is fast!
    time per single call to ProcessCallback::forceUiRefresh()
    - Comparison       0.025 ms
    - Synchronization  0.74 ms (despite complex graph control!)
*/

//Exception class used to abort the "compare" and "sync" process
class AbortProcess {};


enum class AbortTrigger
{
    user,
    program,
};

//GUI may want to abort process
struct AbortCallback
{
    virtual ~AbortCallback() {}
    virtual void userRequestAbort() = 0;
};


struct ProgressStats
{
    int     items = 0;
    int64_t bytes = 0;
};
inline bool operator==(const ProgressStats& lhs, const ProgressStats& rhs) { return lhs.items == rhs.items && lhs.bytes == rhs.bytes; }


//common statistics "everybody" needs
struct Statistics
{
    virtual ~Statistics() {}

    virtual ProcessPhase currentPhase() const = 0;

    virtual ProgressStats getStatsCurrent() const = 0;
    virtual ProgressStats getStatsTotal  () const = 0;

    virtual std::optional<AbortTrigger> getAbortStatus() const = 0;
    virtual const std::wstring& currentStatusText() const = 0;
};


struct ProcessSummary
{
    std::chrono::system_clock::time_point startTime;
    SyncResult resultStatus = SyncResult::aborted;
    std::vector<std::wstring> jobNames; //may be empty
    ProgressStats statsProcessed;
    ProgressStats statsTotal;
    std::chrono::milliseconds totalTime{};
};


//partial callback implementation with common functionality for "batch", "GUI/Compare" and "GUI/Sync"
class StatusHandler : public ProcessCallback, public AbortCallback, public Statistics
{
public:
    //StatusHandler() {}

    //implement parts of ProcessCallback
    void initNewPhase(int itemsTotal, int64_t bytesTotal, ProcessPhase phase) override //(throw X)
    {
        assert((itemsTotal < 0) == (bytesTotal < 0));
        currentPhase_ = phase;
        statsCurrent_ = {};
        statsTotal_ = { itemsTotal, bytesTotal };
    }

    void updateDataProcessed(int itemsDelta, int64_t bytesDelta) override { updateData(statsCurrent_, itemsDelta, bytesDelta); } //note: these methods MUST NOT throw in order
    void updateDataTotal    (int itemsDelta, int64_t bytesDelta) override { updateData(statsTotal_,   itemsDelta, bytesDelta); } //to allow usage within destructors!

    void requestUiUpdate(bool force) final //throw AbortProcess
    {
        if (uiUpdateDue(force))
        {
            const bool abortRequestedBefore = static_cast<bool>(abortRequested_);

            forceUiUpdateNoThrow();

            //triggered by userRequestAbort()
            // => sufficient to evaluate occasionally when uiUpdateDue()!
            // => refresh *before* throwing: support requestUiUpdate() during destruction
            if (abortRequested_)
            {
                if (!abortRequestedBefore)
                    forceUiUpdateNoThrow(); //just once to immediately show the "Stop requested..." status after user clicks cancel
                throw AbortProcess();
            }
        }
    }

    virtual void forceUiUpdateNoThrow() = 0; //noexcept

    void updateStatus(const std::wstring& msg) final //throw AbortProcess
    {
        //assert(!msg.empty()); -> possible, e.g. start of parallel scan
        statusText_ = msg; //update *before* running operations that can throw
        requestUiUpdate(false /*force*/); //throw AbortProcess
    }

    [[noreturn]] void abortProcessNow(AbortTrigger trigger)
    {
        if (!abortRequested_ || trigger == AbortTrigger::user) //AbortTrigger::USER overwrites AbortTrigger::program
            abortRequested_ = trigger;

        forceUiUpdateNoThrow(); //flush GUI to show new cancelled state
        throw AbortProcess();
    }

    //implement AbortCallback
    void userRequestAbort() final
    {
        abortRequested_ = AbortTrigger::user; //may overwrite AbortTrigger::program
    } //called from GUI code: this does NOT call abortProcessNow() immediately, but later when we're out of the C GUI call stack
    //=> don't call forceUiUpdateNoThrow() here!

    //implement Statistics
    ProcessPhase currentPhase() const final { return currentPhase_; }

    ProgressStats getStatsCurrent() const override { return statsCurrent_; }
    ProgressStats getStatsTotal  () const override { return statsTotal_; }

    const std::wstring& currentStatusText() const override { return statusText_; }

    std::optional<AbortTrigger> getAbortStatus() const override { return abortRequested_; }

private:
    void updateData(ProgressStats& stats, int itemsDelta, int64_t bytesDelta)
    {
        assert(stats.items >= 0);
        assert(stats.bytes >= 0);
        stats.items += itemsDelta;
        stats.bytes += bytesDelta;
    }

    ProcessPhase currentPhase_ = ProcessPhase::none;
    ProgressStats statsCurrent_;
    ProgressStats statsTotal_ { -1, -1 };
    std::wstring statusText_;

    std::optional<AbortTrigger> abortRequested_;
};

//------------------------------------------------------------------------------------------

inline
void delayAndCountDown(const std::wstring& operationName, std::chrono::seconds delay, const std::function<void(const std::wstring& msg)>& notifyStatus)
{
    assert(notifyStatus && !zen::endsWith(operationName, L"."));

    const auto delayUntil = std::chrono::steady_clock::now() + delay;
    for (auto now = std::chrono::steady_clock::now(); now < delayUntil; now = std::chrono::steady_clock::now())
    {
        const auto timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(delayUntil - now).count();
        if (notifyStatus)
            notifyStatus(operationName + L"... " + _P("1 sec", "%x sec", numeric::integerDivideRoundUp(timeMs, 1000)));

        std::this_thread::sleep_for(UI_UPDATE_INTERVAL / 2);
    }
}
}

#endif //STATUS_HANDLER_H_81704805908341534
bgstack15