#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 #include #include 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, 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->isActiveFor(m_ignoreInavailabilityAfterStart)) #else && !m_process->isActiveFor(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) == 0) || (remoteDev && (m_enabledNotifications & SyncthingHighLevelNotification::RemoteSyncComplete) == 0)) { return; } // format the notification message const auto message(syncCompleteString(std::vector{ &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