diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/mailsync.cpp | 83 | ||||
| -rw-r--r-- | src/mailsync.h | 44 |
3 files changed, 128 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5f01128..6a2b18e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ add_library(qtmaildir_lib STATIC cidschemehandler.cpp notmuchworker.cpp threadlistmodel.cpp + mailsync.cpp ) target_include_directories(qtmaildir_lib diff --git a/src/mailsync.cpp b/src/mailsync.cpp new file mode 100644 index 0000000..fdc433f --- /dev/null +++ b/src/mailsync.cpp @@ -0,0 +1,83 @@ +#include "mailsync.h" + +MailSync::MailSync(const QString &command, QObject *parent) + : QObject(parent), m_command(command) +{ + // mbsync reports failures on stderr, so both channels go into one log: + // splitting them would leave the pane empty for the runs worth reading. + m_process.setProcessChannelMode(QProcess::MergedChannels); + + connect(&m_process, &QProcess::readyRead, + this, &MailSync::handleReadyRead); + connect(&m_process, &QProcess::finished, + this, &MailSync::handleFinished); + connect(&m_process, &QProcess::errorOccurred, + this, &MailSync::handleError); +} + +bool MailSync::isRunning() const +{ + return m_process.state() != QProcess::NotRunning; +} + +bool MailSync::start() +{ + if (!isAvailable() || isRunning()) + return false; + + // splitCommand handles quoted arguments; running through a shell would make + // a config value into an injection point. + const QStringList parts = QProcess::splitCommand(m_command); + if (parts.isEmpty()) + return false; + + m_log.clear(); + + m_process.setProgram(parts.first()); + m_process.setArguments(parts.mid(1)); + + // Deliberately no waitForStarted(): the spec requires the UI stay usable + // during sync, and a failed launch arrives via errorOccurred() instead. + m_process.start(); + + emit started(); + return true; +} + +void MailSync::handleReadyRead() +{ + const QByteArray data = m_process.readAll(); + if (data.isEmpty()) + return; + + const QString chunk = QString::fromUtf8(data); + m_log += chunk; + emit outputReceived(chunk); +} + +void MailSync::handleFinished(int exitCode, QProcess::ExitStatus status) +{ + // Drain anything buffered at exit. + handleReadyRead(); + + // No guard against a preceding launch failure is needed: verified that + // QProcess emits errorOccurred(FailedToStart) *instead of* finished(), + // not before it. + const bool success = status == QProcess::NormalExit && exitCode == 0; + emit finished(success, exitCode); +} + +void MailSync::handleError(QProcess::ProcessError error) +{ + // Config validates the path at load time, but the script can be deleted or + // its filesystem unmounted afterwards. Without this the spinner would stay + // up forever with nothing explaining why. + if (error != QProcess::FailedToStart) + return; + + const QString message = + QStringLiteral("Failed to start sync command: %1\n").arg(m_command); + m_log += message; + emit outputReceived(message); + emit finished(false, -1); +} diff --git a/src/mailsync.h b/src/mailsync.h new file mode 100644 index 0000000..af4acbc --- /dev/null +++ b/src/mailsync.h @@ -0,0 +1,44 @@ +#pragma once + +#include <QObject> +#include <QProcess> +#include <QString> + +/// Runs the configured external sync command. +/// +/// qtmaildir deliberately does not implement sync itself. The existing script +/// holds a flock that is the shared mutex between the user's cron sync, which +/// runs every 10 minutes, and any manual sync; running the script joins that +/// mutex, whereas a built-in implementation would sit outside it and could run +/// mbsync concurrently with cron, corrupting Maildir UID state. +class MailSync : public QObject +{ + Q_OBJECT +public: + explicit MailSync(const QString &command, QObject *parent = nullptr); + + /// False when no command is configured; the UI disables its Sync button. + bool isAvailable() const { return !m_command.isEmpty(); } + bool isRunning() const; + + /// Returns false if unavailable or already running. A true return means the + /// process was handed to the event loop, not that it launched successfully: + /// a missing binary surfaces asynchronously through finished(false, ...). + bool start(); + + QString log() const { return m_log; } + +signals: + void started(); + void outputReceived(const QString &chunk); + void finished(bool success, int exitCode); + +private: + void handleReadyRead(); + void handleFinished(int exitCode, QProcess::ExitStatus status); + void handleError(QProcess::ProcessError error); + + QString m_command; + QProcess m_process; + QString m_log; +}; |
