File: syncthingnotifier.cpp

package info (click to toggle)
syncthingtray 1.7.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,804 kB
  • sloc: cpp: 31,085; xml: 1,694; java: 570; sh: 81; javascript: 53; makefile: 25
file content (236 lines) | stat: -rw-r--r-- 8,274 bytes parent folder | download
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#include "./syncthingnotifier.h"
#include "./syncthingconnection.h"
#include "./syncthingprocess.h"
#include "./utils.h"

#include "resources/config.h"

#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include "./syncthingservice.h"
#endif

#include <c++utilities/chrono/datetime.h>
#include <c++utilities/io/ansiescapecodes.h>

#include <iostream>

using namespace CppUtilities;

namespace Data {

/*!
 * \class SyncthingNotifier
 * \brief The SyncthingNotifier class emits high-level notification for a given SyncthingConnection.
 *
 * In contrast to the signals provided by the SyncthingConnection class, these signals take further apply
 * further logic and take additional information into account (previous status, service status if known, ...).
 */

/*!
 * \brief Constructs a new SyncthingNotifier instance for the specified \a connection.
 * \remarks Use setEnabledNotifications() to enable notifications (only statusChanged() is always emitted).
 */
SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObject *parent)
    : QObject(parent)
    , m_connection(connection)
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    , m_service(SyncthingService::mainInstance())
#endif
    , m_process(SyncthingProcess::mainInstance())
    , m_enabledNotifications(SyncthingHighLevelNotification::None)
    , m_consideredIntegrations(SyncthingStartupIntegration::None)
    , m_previousStatus(SyncthingStatus::Disconnected)
    , m_ignoreInavailabilityAfterStart(15)
    , m_initialized(false)
    , m_logOnStderr(
          qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_ALL") || qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_LOG_NOTIFICATIONS"))
{
    connect(&connection, &SyncthingConnection::statusChanged, this, &SyncthingNotifier::handleStatusChangedEvent);
    connect(&connection, &SyncthingConnection::dirCompleted, this, &SyncthingNotifier::emitSyncComplete);
    connect(&connection, &SyncthingConnection::newDevAvailable, this, &SyncthingNotifier::handleNewDevEvent);
    connect(&connection, &SyncthingConnection::newDirAvailable, this, &SyncthingNotifier::handleNewDirEvent);
    if (m_process) {
        connect(m_process, &SyncthingProcess::errorOccurred, this, &SyncthingNotifier::handleSyncthingProcessError);
    }
}

void SyncthingNotifier::handleStatusChangedEvent(SyncthingStatus newStatus)
{
    // skip redundant status updates
    if (m_initialized && m_previousStatus == newStatus) {
        return;
    }

    // emit signals
    emit statusChanged(m_previousStatus, newStatus);
    emitConnectedAndDisconnected(newStatus);

    // update status variables
    m_initialized = true;
    m_previousStatus = newStatus;
}

void SyncthingNotifier::handleNewDevEvent(DateTime when, const QString &devId, const QString &address)
{
    CPP_UTILITIES_UNUSED(when)

    // ignore if not enabled
    if (!(m_enabledNotifications && SyncthingHighLevelNotification::NewDevice)) {
        return;
    }

    emit newDevice(devId, log(tr("Device %1 (%2) wants to connect.").arg(devId, address)));
}

void SyncthingNotifier::handleNewDirEvent(DateTime when, const QString &devId, const SyncthingDev *dev, const QString &dirId, const QString &dirLabel)
{
    CPP_UTILITIES_UNUSED(when)

    // ignore if not enabled
    if (!(m_enabledNotifications && SyncthingHighLevelNotification::NewDir)) {
        return;
    }

    // format message
    const auto message([&devId, dev, &dirId, &dirLabel] {
        const auto devPrefix(dev ? (tr("Device ") + dev->displayName()) : (tr("Unknown device ") + devId));
        if (dirLabel.isEmpty()) {
            return devPrefix + tr(" wants to share folder %1.").arg(dirId);
        } else {
            return devPrefix + tr(" wants to share folder %1 (%2).").arg(dirLabel, dirId);
        }
    }());
    emit newDir(devId, dirId, dirLabel, log(message));
}

void SyncthingNotifier::handleSyncthingProcessError(QProcess::ProcessError processError)
{
    if (!(m_enabledNotifications && SyncthingHighLevelNotification::SyncthingProcessError)) {
        return;
    }

    const auto error = m_process->errorString();
    switch (processError) {
    case QProcess::FailedToStart:
        emit syncthingProcessError(tr("Failed to start Syncthing"),
            error.isEmpty() ? tr("Maybe the configured binary path is wrong or the binary is not marked as executable.") : error);
        break;
    case QProcess::Crashed:
        emit syncthingProcessError(tr("Syncthing crashed"), error);
        break;
    default:
        emit syncthingProcessError(tr("Syncthing launcher error occurred"), error);
    }
}

/*!
 * \brief Returns whether a "disconnected" notification should be shown.
 */
bool SyncthingNotifier::isDisconnectRelevant() const
{
    // skip disconnect if not initialized
    if (!m_initialized) {
        return false;
    }

    // skip further considerations if connection is remote
    if (!m_connection.isLocal()) {
        return true;
    }

    // consider process/launcher or systemd unit status
    if ((m_consideredIntegrations && SyncthingStartupIntegration::Process) && m_process && m_process->isManuallyStopped()) {
        return false;
    }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
    if ((m_consideredIntegrations && SyncthingStartupIntegration::Service) && m_service && m_service->isManuallyStopped()) {
        return false;
    }
#endif

    // ignore inavailability after start or standby-wakeup
    if (m_ignoreInavailabilityAfterStart) {
        if (((m_consideredIntegrations && SyncthingStartupIntegration::Process) && m_process && m_process->isRunning())
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
            && (((m_consideredIntegrations && SyncthingStartupIntegration::Service) && m_service && m_service->isSystemdAvailable()
                    && !m_service->isActiveWithoutSleepFor(m_process->activeSince(), m_ignoreInavailabilityAfterStart))
                || !m_process->isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart))
#else
            && !m_process->isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart)
#endif
        ) {
            return false;
        }
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
        if ((m_consideredIntegrations && SyncthingStartupIntegration::Service) && m_service && m_service->isRunning()
            && !m_service->isActiveWithoutSleepFor(m_ignoreInavailabilityAfterStart)) {
            return false;
        }
#endif
    }

    return true;
}

/*!
 * \brief Emits the connected() or disconnected() signal.
 */
void SyncthingNotifier::emitConnectedAndDisconnected(SyncthingStatus newStatus)
{
    // discard event if not enabled
    if (!(m_enabledNotifications && SyncthingHighLevelNotification::ConnectedDisconnected)) {
        return;
    }

    switch (newStatus) {
    case SyncthingStatus::Disconnected:
        if (isDisconnectRelevant()) {
            emit disconnected();
        }
        break;
    default:
        switch (m_previousStatus) {
        case SyncthingStatus::Disconnected:
        case SyncthingStatus::Reconnecting:
            emit connected();
            break;
        default:;
        }
    }
}

/*!
 * \brief Emits the syncComplete() signal.
 */
void SyncthingNotifier::emitSyncComplete(CppUtilities::DateTime when, const SyncthingDir &dir, int index, const SyncthingDev *remoteDev)
{
    CPP_UTILITIES_UNUSED(when)
    CPP_UTILITIES_UNUSED(index)

    // discard event for paused directories/devices
    if (dir.paused || (remoteDev && remoteDev->paused)) {
        return;
    }

    // discard event if not enabled
    if (!m_initialized || (!remoteDev && !(m_enabledNotifications && SyncthingHighLevelNotification::LocalSyncComplete))
        || (remoteDev && !(m_enabledNotifications && SyncthingHighLevelNotification::RemoteSyncComplete))) {
        return;
    }

    // format the notification message
    const auto message(syncCompleteString(std::vector<const SyncthingDir *>{ &dir }, remoteDev));
    if (!message.isEmpty()) {
        emit syncComplete(log(message));
    }
}

const QString &SyncthingNotifier::log(const QString &message)
{
    if (m_logOnStderr) {
        std::cerr << EscapeCodes::Phrases::Info << message.toStdString() << EscapeCodes::Phrases::End;
    }
    return message;
}

} // namespace Data