diff --git a/CMakeLists.txt b/CMakeLists.txt index b8052fa..0bca868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,8 +35,10 @@ set(SOURCE_FILES main.cpp mainwindow.cpp zjuconnectmode.cpp + application/connectionsession.cpp core/corecommandbuilder.cpp core/coreoutputparser.cpp + core/connectionsessionstate.cpp infrastructure/settingsprofileloader.cpp zjuconnectcontroller/zjuconnectcontroller.cpp extrasettingwindow/extrasettingwindow.cpp @@ -136,4 +138,12 @@ if(BUILD_TESTING) target_include_directories(settingsprofileloader_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(settingsprofileloader_test Qt::Core) add_test(NAME settingsprofileloader_test COMMAND settingsprofileloader_test) + + add_executable(connectionsessionstate_test + tests/connectionsessionstate_test.cpp + core/connectionsessionstate.cpp + ) + target_include_directories(connectionsessionstate_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(connectionsessionstate_test Qt::Core) + add_test(NAME connectionsessionstate_test COMMAND connectionsessionstate_test) endif() diff --git a/application/connectionsession.cpp b/application/connectionsession.cpp new file mode 100644 index 0000000..312a659 --- /dev/null +++ b/application/connectionsession.cpp @@ -0,0 +1,141 @@ +#include "connectionsession.h" + +ConnectionSession::ConnectionSession(QObject *parent) + : QObject(parent), + controller(new ZjuConnectController(this)) +{ + reconnectTimer.setSingleShot(true); + + connect(controller, &ZjuConnectController::outputRead, this, &ConnectionSession::outputRead); + connect(controller, &ZjuConnectController::graphCaptcha, this, &ConnectionSession::graphCaptcha); + connect(controller, &ZjuConnectController::smsCode, this, &ConnectionSession::smsCode); + connect(controller, &ZjuConnectController::totpCode, this, &ConnectionSession::totpCode); + connect(controller, &ZjuConnectController::ssoAuth, this, &ConnectionSession::ssoAuth); + connect(controller, &ZjuConnectController::askSudoPass, + this, &ConnectionSession::handleSudoPasswordRequest); + + connect(controller, &ZjuConnectController::error, this, [this](ZJU_ERROR error) + { + sessionState.recordError(error); + }); + connect(controller, &ZjuConnectController::started, this, [this]() + { + sessionState.processStarted(); + emit stateChanged(sessionState.state()); + }); + connect(controller, &ZjuConnectController::finished, + this, &ConnectionSession::handleCoreFinished); + connect(&reconnectTimer, &QTimer::timeout, this, [this]() + { + if (sessionState.state() != ConnectionState::Reconnecting) + { + return; + } + sessionState.beginReconnect(); + emit stateChanged(sessionState.state()); + startCore(); + }); +} + +bool ConnectionSession::start(const ConnectionProfile &profile, const ReconnectPolicy &policy) +{ + if (!sessionState.requestStart(policy)) + { + return false; + } + + currentProfile = profile; + emit stateChanged(sessionState.state()); + startCore(); + return true; +} + +void ConnectionSession::stop() +{ + const ConnectionState previousState = sessionState.state(); + const bool processNeedsStop = sessionState.requestStop(); + if (previousState == ConnectionState::Reconnecting) + { + reconnectTimer.stop(); + emit stateChanged(sessionState.state()); + emit finished(sessionState.error()); + return; + } + + if (processNeedsStop) + { + emit stateChanged(sessionState.state()); + controller->stop(); + } +} + +void ConnectionSession::submitInput(const QByteArray &data) +{ + controller->writeInput(data); +} + +void ConnectionSession::submitSudoPassword(const QString &password, bool remember) +{ + if (password.isEmpty()) + { + stop(); + return; + } + + if (remember) + { + savedSudoPassword = password; + } + sudoPasswordSubmitted = true; + submitInput(password.toUtf8() + "\n"); +} + +ConnectionState ConnectionSession::state() const +{ + return sessionState.state(); +} + +bool ConnectionSession::isActive() const +{ + return sessionState.isActive(); +} + +void ConnectionSession::startCore() +{ + sudoPasswordSubmitted = false; + controller->start(currentProfile); +} + +void ConnectionSession::handleCoreFinished() +{ + if (sessionState.processFinished() == ProcessFinishAction::Reconnect) + { + emit stateChanged(sessionState.state()); + emit reconnectScheduled(sessionState.reconnectDelayMs()); + reconnectTimer.start(sessionState.reconnectDelayMs()); + return; + } + + emit stateChanged(sessionState.state()); + emit finished(sessionState.error()); +} + +void ConnectionSession::handleSudoPasswordRequest() +{ + if (savedSudoPassword.isEmpty()) + { + emit askSudoPass(); + return; + } + + if (sudoPasswordSubmitted) + { + savedSudoPassword.clear(); + emit savedSudoPasswordRejected(); + emit askSudoPass(); + return; + } + + sudoPasswordSubmitted = true; + submitInput(savedSudoPassword.toUtf8() + "\n"); +} diff --git a/application/connectionsession.h b/application/connectionsession.h new file mode 100644 index 0000000..36b1b6e --- /dev/null +++ b/application/connectionsession.h @@ -0,0 +1,51 @@ +#ifndef CONNECTIONSESSION_H +#define CONNECTIONSESSION_H + +#include +#include + +#include "core/connectionprofile.h" +#include "core/connectionsessionstate.h" +#include "zjuconnectcontroller/zjuconnectcontroller.h" + +class ConnectionSession : public QObject +{ +Q_OBJECT + +public: + explicit ConnectionSession(QObject *parent = nullptr); + + bool start(const ConnectionProfile &profile, const ReconnectPolicy &policy); + void stop(); + void submitInput(const QByteArray &data); + void submitSudoPassword(const QString &password, bool remember); + + ConnectionState state() const; + bool isActive() const; + +signals: + void stateChanged(ConnectionState state); + void outputRead(const QString &output); + void graphCaptcha(const QString &graphFile); + void smsCode(bool showSkipSecondaryAuthOption); + void totpCode(); + void ssoAuth(); + void askSudoPass(); + void savedSudoPasswordRejected(); + void reconnectScheduled(int delayMs); + void finished(ZJU_ERROR error); + +private: + void startCore(); + void handleCoreFinished(); + void handleSudoPasswordRequest(); + + ZjuConnectController *controller; + QTimer reconnectTimer; + ConnectionSessionState sessionState; + ConnectionProfile currentProfile; + QString savedSudoPassword; + bool sudoPasswordSubmitted = false; +}; + +#endif // CONNECTIONSESSION_H diff --git a/core/connectionerror.h b/core/connectionerror.h new file mode 100644 index 0000000..8e34a9d --- /dev/null +++ b/core/connectionerror.h @@ -0,0 +1,21 @@ +#ifndef CONNECTIONERROR_H +#define CONNECTIONERROR_H + +enum class ZJU_ERROR +{ + NONE, + INVALID_DETAIL, + BRUTE_FORCE, + OTHER_LOGIN_FAILED, + ACCESS_DENIED, + LISTEN_FAILED, + CLIENT_FAILED, + CAPTCHA_FAILED, + PROGRAM_NOT_FOUND, + INTERACTIVE_ERROR, + AUTH_NOT_AVAILABLE, + AUTH_EXPIRED, + OTHER, +}; + +#endif // CONNECTIONERROR_H diff --git a/core/connectionsessionstate.cpp b/core/connectionsessionstate.cpp new file mode 100644 index 0000000..d0c45cc --- /dev/null +++ b/core/connectionsessionstate.cpp @@ -0,0 +1,106 @@ +#include "connectionsessionstate.h" + +ConnectionState ConnectionSessionState::state() const +{ + return currentState; +} + +ZJU_ERROR ConnectionSessionState::error() const +{ + return currentError; +} + +bool ConnectionSessionState::isActive() const +{ + return currentState == ConnectionState::Starting + || currentState == ConnectionState::Running + || currentState == ConnectionState::Stopping + || currentState == ConnectionState::Reconnecting; +} + +bool ConnectionSessionState::wantsConnection() const +{ + return desiredConnected; +} + +int ConnectionSessionState::reconnectDelayMs() const +{ + return reconnectPolicy.delayMs; +} + +bool ConnectionSessionState::requestStart(const ReconnectPolicy &policy) +{ + if (isActive()) + { + return false; + } + + reconnectPolicy = policy; + desiredConnected = true; + currentError = ZJU_ERROR::NONE; + currentState = ConnectionState::Starting; + return true; +} + +void ConnectionSessionState::processStarted() +{ + if (currentState == ConnectionState::Starting) + { + currentState = ConnectionState::Running; + } +} + +void ConnectionSessionState::recordError(ZJU_ERROR error) +{ + if (currentError == ZJU_ERROR::NONE) + { + currentError = error; + } +} + +bool ConnectionSessionState::requestStop() +{ + desiredConnected = false; + if (currentState == ConnectionState::Reconnecting) + { + currentState = ConnectionState::Disconnected; + return false; + } + if (currentState == ConnectionState::Starting || currentState == ConnectionState::Running) + { + currentState = ConnectionState::Stopping; + return true; + } + return currentState == ConnectionState::Stopping; +} + +ProcessFinishAction ConnectionSessionState::processFinished() +{ + if (desiredConnected + && reconnectPolicy.enabled + && isReconnectable(currentError)) + { + currentState = ConnectionState::Reconnecting; + return ProcessFinishAction::Reconnect; + } + + desiredConnected = false; + currentState = currentError == ZJU_ERROR::NONE + ? ConnectionState::Disconnected + : ConnectionState::Failed; + return ProcessFinishAction::Complete; +} + +void ConnectionSessionState::beginReconnect() +{ + if (currentState == ConnectionState::Reconnecting && desiredConnected) + { + currentError = ZJU_ERROR::NONE; + currentState = ConnectionState::Starting; + } +} + +bool ConnectionSessionState::isReconnectable(ZJU_ERROR error) +{ + return error == ZJU_ERROR::AUTH_EXPIRED || error == ZJU_ERROR::OTHER; +} diff --git a/core/connectionsessionstate.h b/core/connectionsessionstate.h new file mode 100644 index 0000000..8b4d213 --- /dev/null +++ b/core/connectionsessionstate.h @@ -0,0 +1,53 @@ +#ifndef CONNECTIONSESSIONSTATE_H +#define CONNECTIONSESSIONSTATE_H + +#include "connectionerror.h" + +enum class ConnectionState +{ + Disconnected, + Starting, + Running, + Stopping, + Reconnecting, + Failed, +}; + +struct ReconnectPolicy +{ + bool enabled = false; + int delayMs = 1000; +}; + +enum class ProcessFinishAction +{ + Complete, + Reconnect, +}; + +class ConnectionSessionState +{ +public: + ConnectionState state() const; + ZJU_ERROR error() const; + bool isActive() const; + bool wantsConnection() const; + int reconnectDelayMs() const; + + bool requestStart(const ReconnectPolicy &policy); + void processStarted(); + void recordError(ZJU_ERROR error); + bool requestStop(); + ProcessFinishAction processFinished(); + void beginReconnect(); + +private: + static bool isReconnectable(ZJU_ERROR error); + + ConnectionState currentState = ConnectionState::Disconnected; + ZJU_ERROR currentError = ZJU_ERROR::NONE; + ReconnectPolicy reconnectPolicy; + bool desiredConnected = false; +}; + +#endif // CONNECTIONSESSIONSTATE_H diff --git a/mainwindow.cpp b/mainwindow.cpp index 137f239..cc8dca4 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -37,9 +37,7 @@ MainWindow::MainWindow(QWidget *parent) : upgradeSettings(); isFirstTimeSetMode = true; - isZjuConnectLinked = false; isSystemProxySet = false; - zjuConnectError = ZJU_ERROR::NONE; ui->setupUi(this); setupTrayIcon(); @@ -353,7 +351,7 @@ MainWindow::MainWindow(QWidget *parent) : void MainWindow::closeEvent(QCloseEvent *event) { - if (isZjuConnectLinked) + if (connectionSession != nullptr && connectionSession->isActive()) { event->ignore(); hide(); @@ -402,8 +400,6 @@ void MainWindow::clearLog() void MainWindow::resetZjuConnectUi() { - isZjuConnectLinked = false; - zjuConnectError = ZJU_ERROR::NONE; ui->pushButton1->setText("连接服务器"); trayConnectAction->setText("连接服务器"); ui->pushButton2->setText("设置系统代理"); @@ -581,7 +577,7 @@ bool MainWindow::switchProfile(const QString &profileId) return true; } - if (isZjuConnectLinked) + if (connectionSession != nullptr && connectionSession->isActive()) { QMessageBox::warning(this, "切换失败", "请先断开 VPN 连接,再切换配置。"); refreshProfileMenu(); @@ -657,7 +653,7 @@ void MainWindow::renameCurrentProfile() { return; } - if (isZjuConnectLinked) + if (connectionSession != nullptr && connectionSession->isActive()) { QMessageBox::warning(this, "重命名失败", "请先断开 VPN 连接,再重命名配置。"); return; @@ -824,9 +820,10 @@ void MainWindow::cleanUpWhenQuit() void MainWindow::gracefullyQuit() { - if (isZjuConnectLinked) + if (connectionSession != nullptr && connectionSession->isActive()) { - connect(zjuConnectController, &ZjuConnectController::finished, qApp, QApplication::quit); + connect(connectionSession, &ConnectionSession::finished, qApp, + [](ZJU_ERROR) { QApplication::quit(); }); ui->pushButton1->click(); } else @@ -848,10 +845,11 @@ MainWindow::~MainWindow() delete profileManager; } - if (zjuConnectController != nullptr) + if (connectionSession != nullptr) { - disconnect(zjuConnectController, &ZjuConnectController::finished, nullptr, nullptr); - delete zjuConnectController; + disconnect(connectionSession, nullptr, this, nullptr); + delete connectionSession; + connectionSession = nullptr; } delete ui; diff --git a/mainwindow.h b/mainwindow.h index 43b97e5..696786f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -9,6 +9,7 @@ #include #include +#include "application/connectionsession.h" #include "loginwindow/loginwindow.h" #include "sudowindow/sudowindow.h" #include "ssologinwebview/ssologinwebview.h" @@ -41,8 +42,6 @@ signals: void SetModeFinished(); - void WriteToProcess(const QByteArray &data); - protected: void closeEvent(QCloseEvent *e) override; @@ -98,7 +97,7 @@ private: QAction *newProfileAction; QAction *renameProfileAction; QAction *deleteProfileAction; - ZjuConnectController *zjuConnectController = nullptr; + ConnectionSession *connectionSession = nullptr; QNetworkAccessManager *checkUpdateNAM; QNetworkAccessManager *checkCoreUpdateNAM; QSettings *settings; @@ -115,10 +114,7 @@ private: bool isFirstTimeSetMode; - bool isZjuConnectLinked; bool isSystemProxySet; - bool isAutoReconnecting = false; - ZJU_ERROR zjuConnectError; }; #endif //MAINWINDOW_H diff --git a/tests/connectionsessionstate_test.cpp b/tests/connectionsessionstate_test.cpp new file mode 100644 index 0000000..ef19f7a --- /dev/null +++ b/tests/connectionsessionstate_test.cpp @@ -0,0 +1,108 @@ +#include +#include + +#include "core/connectionsessionstate.h" + +namespace +{ +bool normalLifecycle() +{ + ConnectionSessionState session; + if (!session.requestStart({false, 1000}) + || session.state() != ConnectionState::Starting + || !session.isActive()) + { + qCritical() << "normalLifecycle failed at start"; + return false; + } + + session.processStarted(); + if (session.state() != ConnectionState::Running) + { + qCritical() << "normalLifecycle failed at running"; + return false; + } + + if (!session.requestStop() || session.state() != ConnectionState::Stopping) + { + qCritical() << "normalLifecycle failed at stopping"; + return false; + } + + if (session.processFinished() != ProcessFinishAction::Complete + || session.state() != ConnectionState::Disconnected + || session.isActive()) + { + qCritical() << "normalLifecycle failed at completion"; + return false; + } + return true; +} + +bool reconnectsOnlyEligibleFailures() +{ + ConnectionSessionState session; + session.requestStart({true, 2500}); + session.processStarted(); + session.recordError(ZJU_ERROR::AUTH_EXPIRED); + + if (session.processFinished() != ProcessFinishAction::Reconnect + || session.state() != ConnectionState::Reconnecting + || session.reconnectDelayMs() != 2500) + { + qCritical() << "reconnectsOnlyEligibleFailures failed at scheduling"; + return false; + } + + session.beginReconnect(); + if (session.state() != ConnectionState::Starting || session.error() != ZJU_ERROR::NONE) + { + qCritical() << "reconnectsOnlyEligibleFailures failed at restart"; + return false; + } + + session.processStarted(); + session.recordError(ZJU_ERROR::INVALID_DETAIL); + if (session.processFinished() != ProcessFinishAction::Complete + || session.state() != ConnectionState::Failed) + { + qCritical() << "reconnectsOnlyEligibleFailures reconnected an ineligible error"; + return false; + } + return true; +} + +bool keepsFirstErrorAndCancelsPendingReconnect() +{ + ConnectionSessionState session; + session.requestStart({true, 1000}); + session.processStarted(); + session.recordError(ZJU_ERROR::AUTH_EXPIRED); + session.recordError(ZJU_ERROR::OTHER); + if (session.error() != ZJU_ERROR::AUTH_EXPIRED) + { + qCritical() << "keepsFirstErrorAndCancelsPendingReconnect did not keep first error"; + return false; + } + + session.processFinished(); + if (session.requestStop() + || session.state() != ConnectionState::Disconnected + || session.wantsConnection()) + { + qCritical() << "keepsFirstErrorAndCancelsPendingReconnect failed to cancel reconnect"; + return false; + } + return true; +} +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + return normalLifecycle() + && reconnectsOnlyEligibleFailures() + && keepsFirstErrorAndCancelsPendingReconnect() + ? 0 + : 1; +} diff --git a/zjuconnectcontroller/zjuconnectcontroller.cpp b/zjuconnectcontroller/zjuconnectcontroller.cpp index afeac41..5197f3e 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.cpp +++ b/zjuconnectcontroller/zjuconnectcontroller.cpp @@ -1,5 +1,4 @@ #include "zjuconnectcontroller.h" -#include "mainwindow.h" #include "core/corecommandbuilder.h" #include "core/coreoutputparser.h" #include "utils/utils.h" @@ -10,7 +9,7 @@ #include #include -ZjuConnectController::ZjuConnectController(QWidget* parent) : QObject(parent) +ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent) { zjuConnectProcess = new QProcess(this); @@ -21,7 +20,7 @@ ZjuConnectController::ZjuConnectController(QWidget* parent) : QObject(parent) logStream = new QTextStream(logFile); logStream->setEncoding(QStringConverter::Utf8); QString startMsg = "=== Log started at " + QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") + - " with " + QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() + + " with " + QCoreApplication::applicationName() + " " + QCoreApplication::applicationVersion() + " ===\n"; *logStream << startMsg; logStream->flush(); @@ -138,10 +137,6 @@ ZjuConnectController::ZjuConnectController(QWidget* parent) : QObject(parent) emit outputRead(timeString + " 退出原因:" "进程已结束"); emit finished(); }); - - - connect(qobject_cast(parent), &MainWindow::WriteToProcess, this, - [&](const QByteArray &data) { zjuConnectProcess->write(data); }); } QString ZjuConnectController::copyCoreForAppImage(const QString &programPath) @@ -232,7 +227,6 @@ void ZjuConnectController::start(const ConnectionProfile &profile) sudoArgs << programToStart << finalArgs; programToStart = "sudo"; finalArgs = sudoArgs; - enteredSudoPassword = false; } #endif @@ -242,6 +236,10 @@ void ZjuConnectController::start(const ConnectionProfile &profile) { emit finished(); } + else + { + emit started(); + } } void ZjuConnectController::stop() @@ -262,6 +260,11 @@ void ZjuConnectController::stop() } } +void ZjuConnectController::writeInput(const QByteArray &data) +{ + zjuConnectProcess->write(data); +} + ZjuConnectController::~ZjuConnectController() { stop(); diff --git a/zjuconnectcontroller/zjuconnectcontroller.h b/zjuconnectcontroller/zjuconnectcontroller.h index 35f4d96..d57614f 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.h +++ b/zjuconnectcontroller/zjuconnectcontroller.h @@ -3,31 +3,15 @@ #include +#include "core/connectionerror.h" #include "core/connectionprofile.h" -enum class ZJU_ERROR -{ - NONE, - INVALID_DETAIL, - BRUTE_FORCE, - OTHER_LOGIN_FAILED, - ACCESS_DENIED, - LISTEN_FAILED, - CLIENT_FAILED, - CAPTCHA_FAILED, - PROGRAM_NOT_FOUND, - INTERACTIVE_ERROR, - AUTH_NOT_AVAILABLE, - AUTH_EXPIRED, - OTHER, -}; - class ZjuConnectController : public QObject { Q_OBJECT public: - ZjuConnectController(QWidget* parent); + explicit ZjuConnectController(QObject *parent = nullptr); ~ZjuConnectController() override; @@ -35,6 +19,7 @@ public: void stop(); + void writeInput(const QByteArray &data); signals: @@ -52,9 +37,9 @@ signals: void askSudoPass(); - void finished(); + void started(); - void write(const QByteArray &data); + void finished(); private: QString copyCoreForAppImage(const QString &programPath); @@ -69,10 +54,6 @@ private: QTextStream *logStream = nullptr; bool stopRequested = false; -public: - bool savedSudoPassword; - bool enteredSudoPassword; - QString sudoPassword; }; #endif //ZJUCONNECTCONTROLLER_H diff --git a/zjuconnectmode.cpp b/zjuconnectmode.cpp index 692de43..d414717 100644 --- a/zjuconnectmode.cpp +++ b/zjuconnectmode.cpp @@ -17,72 +17,38 @@ void MainWindow::initZjuConnect() { - if (zjuConnectController != nullptr) + if (connectionSession != nullptr) { return; } clearLog(); - zjuConnectController = new ZjuConnectController(this); + connectionSession = new ConnectionSession(this); resetZjuConnectUi(); // 连接服务器 - connect(zjuConnectController, &ZjuConnectController::outputRead, this, + connect(connectionSession, &ConnectionSession::outputRead, this, [&](const QString &output) { ui->logPlainTextEdit->appendPlainText(output.trimmed()); }); - connect(zjuConnectController, &ZjuConnectController::error, this, - [&](ZJU_ERROR err) - { - if (zjuConnectError == ZJU_ERROR::NONE) - { - zjuConnectError = err; - } - }); + connect(connectionSession, &ConnectionSession::savedSudoPasswordRejected, this, + [&]() { addLog("sudo 密码可能有误,不使用记住的密码"); }); - connect(zjuConnectController, &ZjuConnectController::askSudoPass, this, + connect(connectionSession, &ConnectionSession::askSudoPass, this, [&]() { - if (zjuConnectController->savedSudoPassword) - { - if (zjuConnectController->enteredSudoPassword) - { - addLog("sudo 密码可能有误,不使用记住的密码"); - zjuConnectController->savedSudoPassword = false; - zjuConnectController->sudoPassword.clear(); - } - else - { - zjuConnectController->enteredSudoPassword = true; - emit WriteToProcess(zjuConnectController->sudoPassword.toUtf8() + "\n"); - return; - } - } sudoWindow = new SudoWindow(this); connect(sudoWindow, &SudoWindow::sudo, this, [&](const QString &password, bool save) { - if (password.isEmpty()) - { - zjuConnectController->stop(); - } - else - { - if (save) - { - zjuConnectController->savedSudoPassword = true; - zjuConnectController->sudoPassword = password; - } - zjuConnectController->enteredSudoPassword = true; - emit WriteToProcess(password.toUtf8() + "\n"); - } + connectionSession->submitSudoPassword(password, save); }); sudoWindow->show(); }); - connect(zjuConnectController, &ZjuConnectController::graphCaptcha, this, + connect(connectionSession, &ConnectionSession::graphCaptcha, this, [&](const QString &graphFile) { addLog("需要图形验证码"); graphCaptchaWindow = new GraphCaptchaWindow(this); @@ -90,11 +56,11 @@ void MainWindow::initZjuConnect() graphCaptchaWindow->show(); connect(graphCaptchaWindow, &GraphCaptchaWindow::finishCaptcha, this, [&](const QByteArray &captcha) { addLog("图形验证码用户输入:" + captcha); - emit WriteToProcess(captcha + "\n"); + connectionSession->submitInput(captcha + "\n"); }); }); - connect(zjuConnectController, &ZjuConnectController::smsCode, this, [&](bool showSkipSecondaryAuthOption) { + connect(connectionSession, &ConnectionSession::smsCode, this, [&](bool showSkipSecondaryAuthOption) { addLog("需要短信验证码"); QDialog smsCodeDialog(this); @@ -130,20 +96,20 @@ void MainWindow::initZjuConnect() { smsCodeInput.prepend('$'); } - emit WriteToProcess(smsCodeInput + "\n"); + connectionSession->submitInput(smsCodeInput + "\n"); }); - connect(zjuConnectController, &ZjuConnectController::totpCode, this, [&]() { + connect(connectionSession, &ConnectionSession::totpCode, this, [&]() { addLog("需要 TOTP 验证码"); QString totp = QInputDialog::getText(this, "TOTP 验证码", "请输入 TOTP 验证码:"); addLog("TOTP 验证码用户输入:" + totp); - emit WriteToProcess(totp.toLocal8Bit() + "\n"); + connectionSession->submitInput(totp.toLocal8Bit() + "\n"); }); - connect(zjuConnectController, &ZjuConnectController::ssoAuth, this, [&]() { + connect(connectionSession, &ConnectionSession::ssoAuth, this, [&]() { ssoLoginWebView = new SsoLoginWebView(this); connect(ssoLoginWebView, &SsoLoginWebView::loginCompleted, - [=](const QString &url) { emit WriteToProcess(url.toLocal8Bit() + "\n"); }); + [=](const QString &url) { connectionSession->submitInput(url.toLocal8Bit() + "\n"); }); QString serverHost = settings->value("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn").toString(); int serverPort = settings->value("ZJUConnect/ServerPort", 443).toInt(); @@ -164,36 +130,18 @@ void MainWindow::initZjuConnect() ssoLoginWebView->show(); }); - connect(zjuConnectController, &ZjuConnectController::finished, this, [&]() + connect(connectionSession, &ConnectionSession::reconnectScheduled, this, [&](int) + { + addLog("正在尝试重新连接..."); + }); + + connect(connectionSession, &ConnectionSession::finished, this, [&](ZJU_ERROR error) { addLog("VPN 断开!"); - if ( - (zjuConnectError == ZJU_ERROR::AUTH_EXPIRED || zjuConnectError == ZJU_ERROR::OTHER) && - settings->value("Common/AutoReconnect", false).toBool() && - isZjuConnectLinked - ) - { - QTimer::singleShot(settings->value("Common/ReconnectTime", 1).toInt() * 1000, this, [&]() - { - if (isZjuConnectLinked) - { - addLog("正在尝试重新连接..."); - zjuConnectController->stop(); - - isZjuConnectLinked = false; - isAutoReconnecting = true; - ui->pushButton1->click(); - } - }); - - return; - } - - if (zjuConnectError != ZJU_ERROR::NONE) + if (error != ZJU_ERROR::NONE) { showNotification("VPN", "VPN 意外断开!", QSystemTrayIcon::MessageIcon::Warning); } - isZjuConnectLinked = false; ui->pushButton1->setText("连接服务器"); trayConnectAction->setText("连接服务器"); if (isSystemProxySet) @@ -202,7 +150,7 @@ void MainWindow::initZjuConnect() } ui->pushButton2->hide(); - switch (zjuConnectError) + switch (error) { case ZJU_ERROR::INVALID_DETAIL: QMessageBox::critical(this, "错误", "登录失败!\n请检查设置中的网络账号和密码是否设置正确。"); @@ -244,13 +192,12 @@ void MainWindow::initZjuConnect() default: break; } - zjuConnectError = ZJU_ERROR::NONE; }); connect(ui->pushButton1, &QPushButton::clicked, [&]() { - if (!isZjuConnectLinked) + if (!connectionSession->isActive()) { if (settings->contains("ZJUConnect/ServerAddress") && settings->value("ZJUConnect/ServerAddress").toString().isEmpty()) @@ -281,22 +228,23 @@ void MainWindow::initZjuConnect() #endif auto startZjuConnect = [this](const QString &username, const QString &password) { - isZjuConnectLinked = true; - zjuConnectError = ZJU_ERROR::NONE; ui->pushButton1->setText("断开服务器"); trayConnectAction->setText("断开服务器"); ui->pushButton2->show(); - if (!isAutoReconnecting && settings->value("Common/AutoSetProxy", false).toBool()) + if (settings->value("Common/AutoSetProxy", false).toBool()) { ui->pushButton2->click(); } - isAutoReconnecting = false; ConnectionProfile profile = SettingsProfileLoader::load(*settings, currentProfileId, username, password); profile.program = Utils::getCorePath(); - zjuConnectController->start(profile); + const ReconnectPolicy reconnectPolicy{ + settings->value("Common/AutoReconnect", false).toBool(), + settings->value("Common/ReconnectTime", 1).toInt() * 1000 + }; + connectionSession->start(profile, reconnectPolicy); }; if (((protocol == "atrust" && authtype == "psw") || @@ -327,7 +275,7 @@ void MainWindow::initZjuConnect() } else { - zjuConnectController->stop(); + connectionSession->stop(); } }); @@ -377,7 +325,7 @@ void MainWindow::initZjuConnect() Utils::clearSystemProxy(); ui->pushButton2->setText("设置系统代理"); isSystemProxySet = false; - if (!isZjuConnectLinked) + if (!connectionSession->isActive()) { ui->pushButton2->hide(); }