mirror of
https://github.com/chenx-dust/EZ4Connect.git
synced 2026-09-20 10:23:33 +08:00
refactor: 大规模架构重构
This commit is contained in:
114
CMakeLists.txt
114
CMakeLists.txt
@@ -34,32 +34,44 @@ find_package(Qt6 COMPONENTS
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
zjuconnectmode.cpp
|
||||
presentation/main/mainwindow.cpp
|
||||
presentation/main/mainwindowcoordinator.cpp
|
||||
presentation/coordinators/authdialogcoordinator.cpp
|
||||
presentation/coordinators/connectionuicontroller.cpp
|
||||
application/applicationlogger.cpp
|
||||
application/commandlineoptions.cpp
|
||||
application/coreprocess.h
|
||||
application/connectionsession.cpp
|
||||
application/profileservice.cpp
|
||||
application/settingsmigrator.cpp
|
||||
application/systemproxysession.cpp
|
||||
core/corecommandbuilder.cpp
|
||||
core/coreoutputbuffer.cpp
|
||||
core/coreoutputparser.cpp
|
||||
application/defaultsettings.cpp
|
||||
infrastructure/coreprocess/corecommandbuilder.cpp
|
||||
infrastructure/coreprocess/consoleoutputdecoder.cpp
|
||||
infrastructure/coreprocess/coreexecutable.cpp
|
||||
infrastructure/coreprocess/devicetrust.cpp
|
||||
infrastructure/coreprocess/coreoutputbuffer.cpp
|
||||
infrastructure/coreprocess/coreoutputparser.cpp
|
||||
core/connectionsessionstate.cpp
|
||||
infrastructure/corelogfile.cpp
|
||||
infrastructure/settingsprofileloader.cpp
|
||||
infrastructure/platformsystemproxybackend.cpp
|
||||
zjuconnectcontroller/zjuconnectcontroller.cpp
|
||||
extrasettingwindow/extrasettingwindow.cpp
|
||||
settingwindow/settingwindow.cpp
|
||||
loginwindow/loginwindow.cpp
|
||||
sudowindow/sudowindow.cpp
|
||||
ssologinwebview/ssologinwebview.cpp
|
||||
authinfowindow/authinfowindow.cpp
|
||||
graphcaptchawindow/graphcaptchawindow.cpp
|
||||
graphcaptchawindow/captchacanvas.cpp
|
||||
utils/utils.cpp
|
||||
utils/profilemanager.cpp
|
||||
utils/proxysettings.cpp
|
||||
utils/coreversion.cpp
|
||||
utils/trustdevice.cpp
|
||||
infrastructure/logging/corelogfile.cpp
|
||||
infrastructure/platform/autostart.cpp
|
||||
infrastructure/platform/privileges.cpp
|
||||
infrastructure/settings/settingsprofileloader.cpp
|
||||
infrastructure/storage/applicationpaths.cpp
|
||||
infrastructure/update/updatechecker.cpp
|
||||
infrastructure/platform/platformsystemproxybackend.cpp
|
||||
infrastructure/platform/systemproxyfunctions.cpp
|
||||
infrastructure/coreprocess/zjuconnectprocess.cpp
|
||||
presentation/presentationhelpers.cpp
|
||||
presentation/dialogs/extrasettingwindow/extrasettingwindow.cpp
|
||||
presentation/dialogs/settingwindow/settingwindow.cpp
|
||||
presentation/dialogs/loginwindow/loginwindow.cpp
|
||||
presentation/dialogs/sudowindow/sudowindow.cpp
|
||||
presentation/dialogs/ssologinwebview/ssologinwebview.cpp
|
||||
presentation/dialogs/authinfowindow/authinfowindow.cpp
|
||||
presentation/dialogs/graphcaptchawindow/graphcaptchawindow.cpp
|
||||
presentation/dialogs/graphcaptchawindow/captchacanvas.cpp
|
||||
infrastructure/settings/profilemanager.cpp
|
||||
resource.qrc
|
||||
)
|
||||
|
||||
@@ -132,7 +144,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(corelogfile_test
|
||||
tests/corelogfile_test.cpp
|
||||
infrastructure/corelogfile.cpp
|
||||
infrastructure/logging/corelogfile.cpp
|
||||
)
|
||||
target_include_directories(corelogfile_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(corelogfile_test Qt::Core)
|
||||
@@ -140,7 +152,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(corecommandbuilder_test
|
||||
tests/corecommandbuilder_test.cpp
|
||||
core/corecommandbuilder.cpp
|
||||
infrastructure/coreprocess/corecommandbuilder.cpp
|
||||
)
|
||||
target_include_directories(corecommandbuilder_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(corecommandbuilder_test Qt::Core)
|
||||
@@ -148,7 +160,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(coreoutputparser_test
|
||||
tests/coreoutputparser_test.cpp
|
||||
core/coreoutputparser.cpp
|
||||
infrastructure/coreprocess/coreoutputparser.cpp
|
||||
)
|
||||
target_include_directories(coreoutputparser_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(coreoutputparser_test Qt::Core)
|
||||
@@ -156,7 +168,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(coreoutputbuffer_test
|
||||
tests/coreoutputbuffer_test.cpp
|
||||
core/coreoutputbuffer.cpp
|
||||
infrastructure/coreprocess/coreoutputbuffer.cpp
|
||||
)
|
||||
target_include_directories(coreoutputbuffer_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(coreoutputbuffer_test Qt::Core)
|
||||
@@ -164,7 +176,7 @@ if(BUILD_TESTING)
|
||||
|
||||
add_executable(settingsprofileloader_test
|
||||
tests/settingsprofileloader_test.cpp
|
||||
infrastructure/settingsprofileloader.cpp
|
||||
infrastructure/settings/settingsprofileloader.cpp
|
||||
)
|
||||
target_include_directories(settingsprofileloader_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(settingsprofileloader_test Qt::Core)
|
||||
@@ -178,13 +190,55 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(connectionsessionstate_test Qt::Core)
|
||||
add_test(NAME connectionsessionstate_test COMMAND connectionsessionstate_test)
|
||||
|
||||
add_executable(connectionsession_test
|
||||
tests/connectionsession_test.cpp
|
||||
application/coreprocess.h
|
||||
application/connectionsession.cpp
|
||||
core/connectionsessionstate.cpp
|
||||
)
|
||||
target_include_directories(connectionsession_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(connectionsession_test Qt::Core)
|
||||
add_test(NAME connectionsession_test COMMAND connectionsession_test)
|
||||
|
||||
add_executable(systemproxysession_test
|
||||
tests/systemproxysession_test.cpp
|
||||
application/systemproxysession.cpp
|
||||
infrastructure/platformsystemproxybackend.cpp
|
||||
utils/proxysettings.cpp
|
||||
)
|
||||
target_include_directories(systemproxysession_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(systemproxysession_test Qt::Core Qt::Concurrent Qt::Network Qt::Widgets ${PLATFORM_LIBS})
|
||||
target_link_libraries(systemproxysession_test Qt::Core Qt::Concurrent)
|
||||
add_test(NAME systemproxysession_test COMMAND systemproxysession_test)
|
||||
|
||||
add_executable(updatechecker_test
|
||||
tests/updatechecker_test.cpp
|
||||
infrastructure/update/updatechecker.cpp
|
||||
infrastructure/coreprocess/coreexecutable.cpp
|
||||
)
|
||||
target_include_directories(updatechecker_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(updatechecker_test Qt::Core Qt::Gui Qt::Widgets Qt::Network Qt::Core5Compat)
|
||||
add_test(NAME updatechecker_test COMMAND updatechecker_test)
|
||||
|
||||
add_executable(settingsmigrator_test
|
||||
tests/settingsmigrator_test.cpp
|
||||
application/settingsmigrator.cpp
|
||||
application/defaultsettings.cpp
|
||||
)
|
||||
target_include_directories(settingsmigrator_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(settingsmigrator_test Qt::Core)
|
||||
add_test(NAME settingsmigrator_test COMMAND settingsmigrator_test)
|
||||
|
||||
add_executable(profileservice_test
|
||||
tests/profileservice_test.cpp
|
||||
application/profileservice.cpp
|
||||
)
|
||||
target_include_directories(profileservice_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(profileservice_test Qt::Core)
|
||||
add_test(NAME profileservice_test COMMAND profileservice_test)
|
||||
|
||||
add_executable(profilemanager_test
|
||||
tests/profilemanager_test.cpp
|
||||
infrastructure/settings/profilemanager.cpp
|
||||
)
|
||||
target_include_directories(profilemanager_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(profilemanager_test Qt::Core)
|
||||
add_test(NAME profilemanager_test COMMAND profilemanager_test)
|
||||
endif()
|
||||
|
||||
@@ -60,6 +60,11 @@
|
||||
- [X] 上传 AUR 包
|
||||
- [ ] 使用密钥链存储密码等信息
|
||||
|
||||
## 开发
|
||||
|
||||
项目的分层、依赖方向和新增代码归属规则见
|
||||
[架构说明](docs/ARCHITECTURE.md)。
|
||||
|
||||
## 相关项目
|
||||
|
||||
以下均为 EasyConnect 客户端的桌面友好替代品,可按需使用:
|
||||
|
||||
14
application/applicationconstants.h
Normal file
14
application/applicationconstants.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef APPLICATIONCONSTANTS_H
|
||||
#define APPLICATIONCONSTANTS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace ApplicationConstants
|
||||
{
|
||||
const inline QString RepositoryName("chenx-dust/EZ4Connect");
|
||||
const inline QString CoreRepositoryName("Mythologyli/zju-connect");
|
||||
const inline QString ApplicationName("EZ4Connect");
|
||||
constexpr inline int ConfigVersion = 8;
|
||||
}
|
||||
|
||||
#endif // APPLICATIONCONSTANTS_H
|
||||
19
application/commandlineoptions.cpp
Normal file
19
application/commandlineoptions.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include "commandlineoptions.h"
|
||||
|
||||
QString CommandLineOptions::value(const QStringList &arguments, const QString &key)
|
||||
{
|
||||
const QString prefix = key + "=";
|
||||
for (int index = 0; index < arguments.size(); ++index)
|
||||
{
|
||||
const QString &argument = arguments.at(index);
|
||||
if (argument == key)
|
||||
{
|
||||
return index + 1 < arguments.size() ? arguments.at(index + 1) : QString();
|
||||
}
|
||||
if (argument.startsWith(prefix))
|
||||
{
|
||||
return argument.mid(prefix.size());
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
12
application/commandlineoptions.h
Normal file
12
application/commandlineoptions.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef COMMANDLINEOPTIONS_H
|
||||
#define COMMANDLINEOPTIONS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
namespace CommandLineOptions
|
||||
{
|
||||
QString value(const QStringList &arguments, const QString &key);
|
||||
}
|
||||
|
||||
#endif // COMMANDLINEOPTIONS_H
|
||||
@@ -1,29 +1,30 @@
|
||||
#include "connectionsession.h"
|
||||
|
||||
ConnectionSession::ConnectionSession(QObject *parent)
|
||||
ConnectionSession::ConnectionSession(CoreProcess *coreProcess, QObject *parent)
|
||||
: QObject(parent),
|
||||
controller(new ZjuConnectController(this))
|
||||
coreProcess(coreProcess)
|
||||
{
|
||||
coreProcess->setParent(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,
|
||||
connect(coreProcess, &CoreProcess::outputRead, this, &ConnectionSession::outputRead);
|
||||
connect(coreProcess, &CoreProcess::graphCaptcha, this, &ConnectionSession::graphCaptcha);
|
||||
connect(coreProcess, &CoreProcess::smsCode, this, &ConnectionSession::smsCode);
|
||||
connect(coreProcess, &CoreProcess::totpCode, this, &ConnectionSession::totpCode);
|
||||
connect(coreProcess, &CoreProcess::ssoAuth, this, &ConnectionSession::ssoAuth);
|
||||
connect(coreProcess, &CoreProcess::askSudoPass,
|
||||
this, &ConnectionSession::handleSudoPasswordRequest);
|
||||
|
||||
connect(controller, &ZjuConnectController::error, this, [this](ZJU_ERROR error)
|
||||
connect(coreProcess, &CoreProcess::error, this, [this](ZJU_ERROR error)
|
||||
{
|
||||
sessionState.recordError(error);
|
||||
});
|
||||
connect(controller, &ZjuConnectController::started, this, [this]()
|
||||
connect(coreProcess, &CoreProcess::started, this, [this]()
|
||||
{
|
||||
sessionState.processStarted();
|
||||
emit stateChanged(sessionState.state());
|
||||
});
|
||||
connect(controller, &ZjuConnectController::finished,
|
||||
connect(coreProcess, &CoreProcess::finished,
|
||||
this, &ConnectionSession::handleCoreFinished);
|
||||
connect(&reconnectTimer, &QTimer::timeout, this, [this]()
|
||||
{
|
||||
@@ -65,13 +66,13 @@ void ConnectionSession::stop()
|
||||
if (processNeedsStop)
|
||||
{
|
||||
emit stateChanged(sessionState.state());
|
||||
controller->stop();
|
||||
coreProcess->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSession::submitInput(const QByteArray &data)
|
||||
{
|
||||
controller->writeInput(data);
|
||||
coreProcess->writeInput(data);
|
||||
}
|
||||
|
||||
void ConnectionSession::submitSudoPassword(const QString &password, bool remember)
|
||||
@@ -103,7 +104,7 @@ bool ConnectionSession::isActive() const
|
||||
void ConnectionSession::startCore()
|
||||
{
|
||||
sudoPasswordSubmitted = false;
|
||||
controller->start(currentProfile);
|
||||
coreProcess->start(currentProfile);
|
||||
}
|
||||
|
||||
void ConnectionSession::handleCoreFinished()
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "application/coreprocess.h"
|
||||
#include "core/connectionprofile.h"
|
||||
#include "core/connectionsessionstate.h"
|
||||
#include "zjuconnectcontroller/zjuconnectcontroller.h"
|
||||
|
||||
class ConnectionSession : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConnectionSession(QObject *parent = nullptr);
|
||||
explicit ConnectionSession(CoreProcess *coreProcess, QObject *parent = nullptr);
|
||||
|
||||
bool start(const ConnectionProfile &profile, const ReconnectPolicy &policy);
|
||||
void stop();
|
||||
@@ -40,7 +40,7 @@ private:
|
||||
void handleCoreFinished();
|
||||
void handleSudoPasswordRequest();
|
||||
|
||||
ZjuConnectController *controller;
|
||||
CoreProcess *coreProcess;
|
||||
QTimer reconnectTimer;
|
||||
ConnectionSessionState sessionState;
|
||||
ConnectionProfile currentProfile;
|
||||
|
||||
36
application/coreprocess.h
Normal file
36
application/coreprocess.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef COREPROCESS_H
|
||||
#define COREPROCESS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/connectionerror.h"
|
||||
#include "core/connectionprofile.h"
|
||||
|
||||
class CoreProcess : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoreProcess(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
~CoreProcess() override = default;
|
||||
|
||||
virtual void start(const ConnectionProfile &profile) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void writeInput(const QByteArray &data) = 0;
|
||||
|
||||
signals:
|
||||
void error(ZJU_ERROR error);
|
||||
void outputRead(const QString &output);
|
||||
void graphCaptcha(const QString &graphFile);
|
||||
void smsCode(bool showSkipSecondaryAuthOption);
|
||||
void totpCode();
|
||||
void ssoAuth();
|
||||
void askSudoPass();
|
||||
void started();
|
||||
void finished();
|
||||
};
|
||||
|
||||
#endif // COREPROCESS_H
|
||||
65
application/defaultsettings.cpp
Normal file
65
application/defaultsettings.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#include "application/defaultsettings.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#include "application/applicationconstants.h"
|
||||
|
||||
void DefaultSettings::reset(QSettings &settings)
|
||||
{
|
||||
settings.setValue("Credential/Username", "");
|
||||
settings.setValue("Credential/Password", "");
|
||||
settings.setValue("Credential/TOTPSecret", "");
|
||||
|
||||
settings.setValue("Common/ConnectAfterStart", false);
|
||||
settings.setValue("Common/CheckUpdateAfterStart", false);
|
||||
settings.setValue("Common/AutoSetProxy", false);
|
||||
settings.setValue("Common/ReconnectTime", 1);
|
||||
settings.setValue("Common/AutoReconnect", false);
|
||||
settings.setValue("Common/SystemProxyBypass", "");
|
||||
|
||||
settings.setValue("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn");
|
||||
settings.setValue("ZJUConnect/ServerPort", 443);
|
||||
settings.setValue("ZJUConnect/DNS", "");
|
||||
settings.setValue("ZJUConnect/DNSAuto", true);
|
||||
settings.setValue("ZJUConnect/SecondaryDNS", "");
|
||||
settings.setValue("ZJUConnect/DNSTTL", 3600);
|
||||
settings.setValue("ZJUConnect/SOCKS5Port", 11080);
|
||||
settings.setValue("ZJUConnect/HTTPPort", 11081);
|
||||
settings.setValue("ZJUConnect/ShadowsocksURL", "");
|
||||
settings.setValue("ZJUConnect/DialDirectProxy", "");
|
||||
settings.setValue("ZJUConnect/UpdateBestNodesInterval", 300);
|
||||
|
||||
settings.setValue("ZJUConnect/Protocol", "atrust");
|
||||
settings.setValue("ZJUConnect/LoginDomain", "hitcas");
|
||||
settings.setValue("ZJUConnect/AuthType", "cas");
|
||||
settings.setValue("ZJUConnect/LoginURL", "");
|
||||
settings.setValue("ZJUConnect/PhoneCountryCode", "86");
|
||||
settings.setValue("ZJUConnect/PhoneNumber", "");
|
||||
|
||||
settings.setValue("ZJUConnect/MultiLine", false);
|
||||
settings.setValue("ZJUConnect/KeepAlive", false);
|
||||
settings.setValue("ZJUConnect/KeepAliveURL", "");
|
||||
settings.setValue("ZJUConnect/BindInterface", "");
|
||||
settings.setValue("ZJUConnect/OutsideAccess", false);
|
||||
settings.setValue("ZJUConnect/SkipDomainResource", false);
|
||||
settings.setValue("ZJUConnect/DisableServerConfig", false);
|
||||
settings.setValue("ZJUConnect/ProxyAll", false);
|
||||
settings.setValue("ZJUConnect/DisableZJUDNS", false);
|
||||
settings.setValue("ZJUConnect/ZJUDefault", false);
|
||||
settings.setValue("ZJUConnect/Debug", false);
|
||||
|
||||
settings.setValue("ZJUConnect/TUNMode", false);
|
||||
settings.setValue("ZJUConnect/AddRoute", false);
|
||||
settings.setValue("ZJUConnect/DNSHijack", false);
|
||||
settings.setValue("ZJUConnect/FakeIP", false);
|
||||
settings.setValue("ZJUConnect/TCPTunnelMode", false);
|
||||
settings.setValue("ZJUConnect/AutoDetectInterface", false);
|
||||
|
||||
settings.setValue("ZJUConnect/TCPPortForwarding", "");
|
||||
settings.setValue("ZJUConnect/UDPPortForwarding", "");
|
||||
settings.setValue("ZJUConnect/CustomDNS", "");
|
||||
settings.setValue("ZJUConnect/CustomProxyDomain", "");
|
||||
settings.setValue("ZJUConnect/ExtraArguments", "");
|
||||
|
||||
settings.setValue("Common/ConfigVersion", ApplicationConstants::ConfigVersion);
|
||||
}
|
||||
11
application/defaultsettings.h
Normal file
11
application/defaultsettings.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef DEFAULTSETTINGS_H
|
||||
#define DEFAULTSETTINGS_H
|
||||
|
||||
class QSettings;
|
||||
|
||||
namespace DefaultSettings
|
||||
{
|
||||
void reset(QSettings &settings);
|
||||
}
|
||||
|
||||
#endif // DEFAULTSETTINGS_H
|
||||
30
application/profilebackend.h
Normal file
30
application/profilebackend.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef PROFILEBACKEND_H
|
||||
#define PROFILEBACKEND_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class ProfileBackend
|
||||
{
|
||||
public:
|
||||
virtual ~ProfileBackend() = default;
|
||||
|
||||
virtual QStringList listProfiles() const = 0;
|
||||
virtual QString activeProfile() const = 0;
|
||||
virtual bool setActiveProfile(const QString &profileId) const = 0;
|
||||
virtual QString profilePath(const QString &profileId) const = 0;
|
||||
virtual QString createProfile(
|
||||
const QString &requestedName,
|
||||
const QString &sourcePath = {}
|
||||
) const = 0;
|
||||
virtual bool renameProfile(
|
||||
const QString &oldId,
|
||||
const QString &newId
|
||||
) const = 0;
|
||||
virtual bool removeProfile(const QString &profileId) const = 0;
|
||||
virtual QString normalizeProfileId(const QString &name) const = 0;
|
||||
virtual bool silentStartEnabled() const = 0;
|
||||
virtual void setAutoStartEnabled(bool enabled) const = 0;
|
||||
};
|
||||
|
||||
#endif // PROFILEBACKEND_H
|
||||
181
application/profileservice.cpp
Normal file
181
application/profileservice.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "profileservice.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "application/profilebackend.h"
|
||||
|
||||
ProfileService::ProfileService(
|
||||
std::unique_ptr<ProfileBackend> backend,
|
||||
const QString &overrideConfigPath,
|
||||
QObject *parent
|
||||
)
|
||||
: QObject(parent),
|
||||
profileBackend(std::move(backend)),
|
||||
currentSettings(nullptr),
|
||||
overrideConfiguration(!overrideConfigPath.isEmpty())
|
||||
{
|
||||
if (!overrideConfiguration)
|
||||
{
|
||||
currentId = profileBackend->activeProfile();
|
||||
currentSettings = new QSettings(
|
||||
profileBackend->profilePath(currentId),
|
||||
QSettings::IniFormat
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentId = "custom";
|
||||
currentSettings = new QSettings(overrideConfigPath, QSettings::IniFormat);
|
||||
}
|
||||
}
|
||||
|
||||
ProfileService::~ProfileService()
|
||||
{
|
||||
currentSettings->sync();
|
||||
delete currentSettings;
|
||||
}
|
||||
|
||||
QSettings *ProfileService::settings() const
|
||||
{
|
||||
return currentSettings;
|
||||
}
|
||||
|
||||
QString ProfileService::currentProfileId() const
|
||||
{
|
||||
return currentId;
|
||||
}
|
||||
|
||||
QStringList ProfileService::profiles() const
|
||||
{
|
||||
return profileBackend->listProfiles();
|
||||
}
|
||||
|
||||
bool ProfileService::usesOverrideConfiguration() const
|
||||
{
|
||||
return overrideConfiguration;
|
||||
}
|
||||
|
||||
bool ProfileService::switchTo(const QString &profileId)
|
||||
{
|
||||
if (!overrideConfiguration && profileId == currentId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(profileBackend->profilePath(profileId)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSettings->sync();
|
||||
if (!profileBackend->setActiveProfile(profileId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
delete currentSettings;
|
||||
currentSettings = new QSettings(
|
||||
profileBackend->profilePath(profileId),
|
||||
QSettings::IniFormat
|
||||
);
|
||||
currentId = profileId;
|
||||
overrideConfiguration = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ProfileService::createAndSwitch(const QString &requestedName)
|
||||
{
|
||||
currentSettings->sync();
|
||||
const QString profileId = profileBackend->createProfile(
|
||||
requestedName,
|
||||
currentSettings->fileName()
|
||||
);
|
||||
if (profileId.isEmpty() || !switchTo(profileId))
|
||||
{
|
||||
if (!profileId.isEmpty())
|
||||
{
|
||||
profileBackend->removeProfile(profileId);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return profileId;
|
||||
}
|
||||
|
||||
bool ProfileService::renameCurrent(const QString &newProfileId)
|
||||
{
|
||||
if (currentId.isEmpty() || overrideConfiguration)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSettings->sync();
|
||||
if (!profileBackend->renameProfile(currentId, newProfileId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString previousId = currentId;
|
||||
if (!profileBackend->setActiveProfile(newProfileId))
|
||||
{
|
||||
profileBackend->renameProfile(newProfileId, previousId);
|
||||
return false;
|
||||
}
|
||||
|
||||
currentId = newProfileId;
|
||||
delete currentSettings;
|
||||
currentSettings = new QSettings(
|
||||
profileBackend->profilePath(currentId),
|
||||
QSettings::IniFormat
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProfileService::removeCurrentAndSwitchToDefault()
|
||||
{
|
||||
if (currentId.isEmpty() || overrideConfiguration)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString removedProfileId = currentId;
|
||||
const QString defaultProfilePath = profileBackend->profilePath("");
|
||||
if (!QFileInfo::exists(defaultProfilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentSettings->sync();
|
||||
if (!profileBackend->setActiveProfile(""))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!profileBackend->removeProfile(removedProfileId))
|
||||
{
|
||||
profileBackend->setActiveProfile(removedProfileId);
|
||||
return false;
|
||||
}
|
||||
|
||||
delete currentSettings;
|
||||
currentSettings = new QSettings(defaultProfilePath, QSettings::IniFormat);
|
||||
currentId.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ProfileService::normalizeProfileId(const QString &name) const
|
||||
{
|
||||
return profileBackend->normalizeProfileId(name);
|
||||
}
|
||||
|
||||
bool ProfileService::silentStartEnabled() const
|
||||
{
|
||||
return profileBackend->silentStartEnabled();
|
||||
}
|
||||
|
||||
void ProfileService::migrateAutoStartSetting(bool enabled)
|
||||
{
|
||||
profileBackend->setAutoStartEnabled(enabled);
|
||||
}
|
||||
45
application/profileservice.h
Normal file
45
application/profileservice.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef PROFILESERVICE_H
|
||||
#define PROFILESERVICE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class ProfileBackend;
|
||||
class QSettings;
|
||||
|
||||
class ProfileService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProfileService(
|
||||
std::unique_ptr<ProfileBackend> backend,
|
||||
const QString &overrideConfigPath = {},
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
~ProfileService() override;
|
||||
|
||||
QSettings *settings() const;
|
||||
QString currentProfileId() const;
|
||||
QStringList profiles() const;
|
||||
bool usesOverrideConfiguration() const;
|
||||
|
||||
bool switchTo(const QString &profileId);
|
||||
QString createAndSwitch(const QString &requestedName);
|
||||
bool renameCurrent(const QString &newProfileId);
|
||||
bool removeCurrentAndSwitchToDefault();
|
||||
|
||||
QString normalizeProfileId(const QString &name) const;
|
||||
bool silentStartEnabled() const;
|
||||
void migrateAutoStartSetting(bool enabled);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ProfileBackend> profileBackend;
|
||||
QSettings *currentSettings;
|
||||
QString currentId;
|
||||
bool overrideConfiguration;
|
||||
};
|
||||
|
||||
#endif // PROFILESERVICE_H
|
||||
44
application/settingsmigrator.cpp
Normal file
44
application/settingsmigrator.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "settingsmigrator.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#include "application/applicationconstants.h"
|
||||
#include "application/defaultsettings.h"
|
||||
|
||||
SettingsMigrationAction SettingsMigrator::prepare(QSettings &settings)
|
||||
{
|
||||
const int configVersion = settings.value("Common/ConfigVersion", -1).toInt();
|
||||
if (configVersion == -1)
|
||||
{
|
||||
DefaultSettings::reset(settings);
|
||||
return SettingsMigrationAction::None;
|
||||
}
|
||||
if (configVersion == 4)
|
||||
{
|
||||
settings.setValue("ZJUConnect/Protocol", "easyconnect");
|
||||
return SettingsMigrationAction::None;
|
||||
}
|
||||
if (configVersion == 6)
|
||||
{
|
||||
return SettingsMigrationAction::MigrateAutoStart;
|
||||
}
|
||||
if (configVersion < ApplicationConstants::ConfigVersion)
|
||||
{
|
||||
return SettingsMigrationAction::RecommendReset;
|
||||
}
|
||||
return SettingsMigrationAction::None;
|
||||
}
|
||||
|
||||
void SettingsMigrator::finish(QSettings &settings, bool resetToDefaults)
|
||||
{
|
||||
if (resetToDefaults)
|
||||
{
|
||||
settings.clear();
|
||||
DefaultSettings::reset(settings);
|
||||
}
|
||||
settings.setValue(
|
||||
"Common/ConfigVersion",
|
||||
ApplicationConstants::ConfigVersion
|
||||
);
|
||||
settings.sync();
|
||||
}
|
||||
20
application/settingsmigrator.h
Normal file
20
application/settingsmigrator.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef SETTINGSMIGRATOR_H
|
||||
#define SETTINGSMIGRATOR_H
|
||||
|
||||
class QSettings;
|
||||
|
||||
enum class SettingsMigrationAction
|
||||
{
|
||||
None,
|
||||
MigrateAutoStart,
|
||||
RecommendReset
|
||||
};
|
||||
|
||||
class SettingsMigrator
|
||||
{
|
||||
public:
|
||||
static SettingsMigrationAction prepare(QSettings &settings);
|
||||
static void finish(QSettings &settings, bool resetToDefaults);
|
||||
};
|
||||
|
||||
#endif // SETTINGSMIGRATOR_H
|
||||
@@ -2,13 +2,6 @@
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "infrastructure/platformsystemproxybackend.h"
|
||||
|
||||
SystemProxySession::SystemProxySession(QObject *parent)
|
||||
: SystemProxySession(std::make_unique<PlatformSystemProxyBackend>(), parent)
|
||||
{
|
||||
}
|
||||
|
||||
SystemProxySession::SystemProxySession(
|
||||
std::unique_ptr<SystemProxyBackend> backend,
|
||||
QObject *parent
|
||||
|
||||
@@ -13,7 +13,6 @@ class SystemProxySession : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SystemProxySession(QObject *parent = nullptr);
|
||||
explicit SystemProxySession(std::unique_ptr<SystemProxyBackend> backend, QObject *parent = nullptr);
|
||||
~SystemProxySession() override;
|
||||
|
||||
|
||||
43
docs/ARCHITECTURE.md
Normal file
43
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 架构说明
|
||||
|
||||
EZ4Connect 使用轻量级分层架构。界面层通过 Coordinator 组合应用服务和平台实现,避免
|
||||
`MainWindow` 直接承担连接进程、配置存储及认证窗口的生命周期。
|
||||
|
||||
## 目录职责
|
||||
|
||||
```text
|
||||
core/ 连接领域模型与状态机,不依赖界面和平台实现
|
||||
application/ 用例、会话及基础设施端口
|
||||
infrastructure/ QProcess、QSettings、系统代理、文件和更新检查等实现
|
||||
presentation/ Qt Widgets 界面、对话框和界面流程 Coordinator
|
||||
tests/ 按端口或纯逻辑边界测试各层行为
|
||||
```
|
||||
|
||||
依赖方向为:
|
||||
|
||||
```text
|
||||
presentation -> application -> core
|
||||
| ^
|
||||
v |
|
||||
infrastructure -------+
|
||||
```
|
||||
|
||||
`application` 不引用 `presentation` 或具体的 `infrastructure` 类型。具体实现统一由
|
||||
`MainWindowCoordinator` 创建并注入:
|
||||
|
||||
- `CoreProcess` → `ZjuConnectProcess`
|
||||
- `SystemProxyBackend` → `PlatformSystemProxyBackend`
|
||||
- `ProfileBackend` → `ProfileManager`
|
||||
|
||||
## 主要流程
|
||||
|
||||
- `MainWindowCoordinator` 是主界面的组合入口,并维护 VPN 与系统代理之间的生命周期联动。
|
||||
- `ConnectionUiController` 处理连接、断开、系统代理按钮及错误展示。
|
||||
- `AuthDialogCoordinator` 管理登录、sudo、验证码、TOTP 和 SSO 对话框。
|
||||
- `ConnectionSession` 负责核心进程、重连和连接状态,不依赖具体 `QProcess` 实现。
|
||||
- `ProfileService` 持有当前配置上下文,`SettingsMigrator` 负责配置版本迁移。
|
||||
|
||||
新增代码应按“变化原因”归位:界面行为放入 `presentation`,用例状态放入
|
||||
`application`,平台或文件 I/O 放入 `infrastructure`,可独立验证的连接规则放入
|
||||
`core`。不要重新引入通用 `utils` 目录;只有跨多个职责且没有明确归属的代码才需要新建
|
||||
共享模块。
|
||||
46
infrastructure/coreprocess/consoleoutputdecoder.cpp
Normal file
46
infrastructure/coreprocess/consoleoutputdecoder.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "consoleoutputdecoder.h"
|
||||
|
||||
#include <QTextCodec>
|
||||
|
||||
QString ConsoleOutputDecoder::decode(const QByteArray &bytes)
|
||||
{
|
||||
static QString encoding;
|
||||
if (encoding == "UTF-8")
|
||||
{
|
||||
return QTextCodec::codecForName("UTF-8")->toUnicode(bytes);
|
||||
}
|
||||
if (encoding == "GBK")
|
||||
{
|
||||
return QTextCodec::codecForName("GBK")->toUnicode(bytes);
|
||||
}
|
||||
if (encoding == "locale")
|
||||
{
|
||||
return QString::fromLocal8Bit(bytes);
|
||||
}
|
||||
|
||||
QTextCodec *utf8Codec = QTextCodec::codecForName("UTF-8");
|
||||
const QString utf8 = utf8Codec->toUnicode(bytes);
|
||||
const QByteArray utf8RoundTrip = utf8Codec->fromUnicode(utf8);
|
||||
|
||||
QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");
|
||||
const QString gbk = gbkCodec->toUnicode(bytes);
|
||||
const QByteArray gbkRoundTrip = gbkCodec->fromUnicode(gbk);
|
||||
|
||||
if (utf8RoundTrip == bytes && gbkRoundTrip != bytes)
|
||||
{
|
||||
encoding = "UTF-8";
|
||||
return utf8;
|
||||
}
|
||||
if (gbkRoundTrip == bytes && utf8RoundTrip != bytes)
|
||||
{
|
||||
encoding = "GBK";
|
||||
return gbk;
|
||||
}
|
||||
if (gbkRoundTrip == bytes && utf8RoundTrip == bytes)
|
||||
{
|
||||
return utf8;
|
||||
}
|
||||
|
||||
encoding = "locale";
|
||||
return QString::fromLocal8Bit(bytes);
|
||||
}
|
||||
12
infrastructure/coreprocess/consoleoutputdecoder.h
Normal file
12
infrastructure/coreprocess/consoleoutputdecoder.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef CONSOLEOUTPUTDECODER_H
|
||||
#define CONSOLEOUTPUTDECODER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
namespace ConsoleOutputDecoder
|
||||
{
|
||||
QString decode(const QByteArray &bytes);
|
||||
}
|
||||
|
||||
#endif // CONSOLEOUTPUTDECODER_H
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "connectionprofile.h"
|
||||
#include "core/connectionprofile.h"
|
||||
|
||||
struct CoreRuntimePaths
|
||||
{
|
||||
44
infrastructure/coreprocess/coreexecutable.cpp
Normal file
44
infrastructure/coreprocess/coreexecutable.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "coreexecutable.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QSysInfo>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
QString CoreExecutable::path()
|
||||
{
|
||||
const QString fileName =
|
||||
QSysInfo::productType() == "windows" ? "zju-connect.exe" : "zju-connect";
|
||||
const QString bundledPath = QCoreApplication::applicationDirPath() + "/" + fileName;
|
||||
return QFileInfo::exists(bundledPath) ? bundledPath : fileName;
|
||||
}
|
||||
|
||||
QString CoreExecutable::version(QObject *parent)
|
||||
{
|
||||
QProcess process(parent);
|
||||
process.start(path(), {"-version"});
|
||||
if (!process.waitForStarted())
|
||||
{
|
||||
throw std::runtime_error("核心无法启动");
|
||||
}
|
||||
if (!process.waitForFinished())
|
||||
{
|
||||
throw std::runtime_error("核心运行超时");
|
||||
}
|
||||
|
||||
const QByteArray errorOutput = process.readAllStandardError();
|
||||
if (!errorOutput.isEmpty())
|
||||
{
|
||||
throw std::runtime_error(errorOutput);
|
||||
}
|
||||
|
||||
const QString output = process.readAllStandardOutput();
|
||||
const QString prefix("ZJU Connect v");
|
||||
if (!output.startsWith(prefix))
|
||||
{
|
||||
throw std::runtime_error("无法解析核心版本号");
|
||||
}
|
||||
return output.mid(prefix.size()).trimmed();
|
||||
}
|
||||
14
infrastructure/coreprocess/coreexecutable.h
Normal file
14
infrastructure/coreprocess/coreexecutable.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef COREEXECUTABLE_H
|
||||
#define COREEXECUTABLE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QObject;
|
||||
|
||||
namespace CoreExecutable
|
||||
{
|
||||
QString path();
|
||||
QString version(QObject *parent);
|
||||
}
|
||||
|
||||
#endif // COREEXECUTABLE_H
|
||||
54
infrastructure/coreprocess/devicetrust.cpp
Normal file
54
infrastructure/coreprocess/devicetrust.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "devicetrust.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "infrastructure/coreprocess/coreexecutable.h"
|
||||
#include "infrastructure/storage/applicationpaths.h"
|
||||
|
||||
void DeviceTrust::set(
|
||||
QObject *parent,
|
||||
const QString &protocol,
|
||||
const QString &server,
|
||||
int port,
|
||||
const QString &profileId,
|
||||
bool trusted
|
||||
)
|
||||
{
|
||||
QStringList arguments;
|
||||
if (!protocol.isEmpty())
|
||||
{
|
||||
arguments << "-protocol" << protocol;
|
||||
}
|
||||
if (!server.isEmpty())
|
||||
{
|
||||
arguments << "-server" << server;
|
||||
}
|
||||
if (port != 0)
|
||||
{
|
||||
arguments << "-port" << QString::number(port);
|
||||
}
|
||||
arguments << "-client-data-file" << ApplicationPaths::clientDataFile(profileId);
|
||||
arguments << (trusted ? "-trust-device" : "-untrust-device");
|
||||
|
||||
QProcess process(parent);
|
||||
process.start(CoreExecutable::path(), arguments);
|
||||
if (!process.waitForStarted())
|
||||
{
|
||||
throw std::runtime_error("核心无法启动");
|
||||
}
|
||||
if (!process.waitForFinished())
|
||||
{
|
||||
throw std::runtime_error("核心运行超时");
|
||||
}
|
||||
|
||||
const QByteArray errorOutput = process.readAllStandardError();
|
||||
const QByteArray expected =
|
||||
trusted ? QByteArray("Device trusted successfully")
|
||||
: QByteArray("Device untrusted successfully");
|
||||
if (!errorOutput.contains(expected))
|
||||
{
|
||||
throw std::runtime_error(errorOutput);
|
||||
}
|
||||
}
|
||||
20
infrastructure/coreprocess/devicetrust.h
Normal file
20
infrastructure/coreprocess/devicetrust.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef DEVICETRUST_H
|
||||
#define DEVICETRUST_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QObject;
|
||||
|
||||
namespace DeviceTrust
|
||||
{
|
||||
void set(
|
||||
QObject *parent,
|
||||
const QString &protocol,
|
||||
const QString &server,
|
||||
int port,
|
||||
const QString &profileId,
|
||||
bool trusted
|
||||
);
|
||||
}
|
||||
|
||||
#endif // DEVICETRUST_H
|
||||
@@ -1,14 +1,16 @@
|
||||
#include "zjuconnectcontroller.h"
|
||||
#include "core/corecommandbuilder.h"
|
||||
#include "core/coreoutputparser.h"
|
||||
#include "utils/utils.h"
|
||||
#include "zjuconnectprocess.h"
|
||||
#include "infrastructure/coreprocess/corecommandbuilder.h"
|
||||
#include "infrastructure/coreprocess/consoleoutputdecoder.h"
|
||||
#include "infrastructure/coreprocess/coreoutputparser.h"
|
||||
#include "infrastructure/platform/privileges.h"
|
||||
#include "infrastructure/storage/applicationpaths.h"
|
||||
#include <qcontainerfwd.h>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
|
||||
ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent)
|
||||
ZjuConnectProcess::ZjuConnectProcess(QObject *parent) : CoreProcess(parent)
|
||||
{
|
||||
zjuConnectProcess = new QProcess(this);
|
||||
|
||||
@@ -48,7 +50,7 @@ ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent)
|
||||
});
|
||||
}
|
||||
|
||||
void ZjuConnectController::processOutput(CoreOutputBuffer &buffer, const QByteArray &data, bool flushPending)
|
||||
void ZjuConnectProcess::processOutput(CoreOutputBuffer &buffer, const QByteArray &data, bool flushPending)
|
||||
{
|
||||
QList<QByteArray> lines = buffer.append(data);
|
||||
|
||||
@@ -63,7 +65,7 @@ void ZjuConnectController::processOutput(CoreOutputBuffer &buffer, const QByteAr
|
||||
processOutputLines(lines);
|
||||
}
|
||||
|
||||
void ZjuConnectController::processOutputLines(const QList<QByteArray> &lines)
|
||||
void ZjuConnectProcess::processOutputLines(const QList<QByteArray> &lines)
|
||||
{
|
||||
if (lines.isEmpty())
|
||||
{
|
||||
@@ -74,7 +76,7 @@ void ZjuConnectController::processOutputLines(const QList<QByteArray> &lines)
|
||||
outputLines.reserve(lines.size());
|
||||
for (const QByteArray &line : lines)
|
||||
{
|
||||
outputLines.append(Utils::consoleOutputToQString(line));
|
||||
outputLines.append(ConsoleOutputDecoder::decode(line));
|
||||
}
|
||||
|
||||
const QString output = outputLines.join('\n');
|
||||
@@ -141,7 +143,7 @@ void ZjuConnectController::processOutputLines(const QList<QByteArray> &lines)
|
||||
}
|
||||
}
|
||||
|
||||
QString ZjuConnectController::copyCoreForAppImage(const QString &programPath)
|
||||
QString ZjuConnectProcess::copyCoreForAppImage(const QString &programPath)
|
||||
{
|
||||
#if defined(Q_OS_UNIX)
|
||||
static QString cachedSourcePath;
|
||||
@@ -185,7 +187,7 @@ QString ZjuConnectController::copyCoreForAppImage(const QString &programPath)
|
||||
#endif
|
||||
}
|
||||
|
||||
void ZjuConnectController::start(const ConnectionProfile &profile)
|
||||
void ZjuConnectProcess::start(const ConnectionProfile &profile)
|
||||
{
|
||||
CoreRuntimePaths runtimePaths;
|
||||
|
||||
@@ -199,7 +201,7 @@ void ZjuConnectController::start(const ConnectionProfile &profile)
|
||||
}
|
||||
graphFile = tempDir->filePath("graph.jpg");
|
||||
runtimePaths.graphCodeFile = graphFile;
|
||||
runtimePaths.clientDataFile = Utils::getClientDataPath(profile.profileId);
|
||||
runtimePaths.clientDataFile = ApplicationPaths::clientDataFile(profile.profileId);
|
||||
}
|
||||
|
||||
const CoreCommand command = CoreCommandBuilder::build(profile, runtimePaths);
|
||||
@@ -218,7 +220,7 @@ void ZjuConnectController::start(const ConnectionProfile &profile)
|
||||
QStringList finalArgs = command.arguments;
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
if (profile.tunnel.tunMode && !Utils::isRunningAsAdmin())
|
||||
if (profile.tunnel.tunMode && !Privileges::isElevated())
|
||||
{
|
||||
programToStart = copyCoreForAppImage(programToStart);
|
||||
|
||||
@@ -243,7 +245,7 @@ void ZjuConnectController::start(const ConnectionProfile &profile)
|
||||
}
|
||||
}
|
||||
|
||||
void ZjuConnectController::stop()
|
||||
void ZjuConnectProcess::stop()
|
||||
{
|
||||
if (zjuConnectProcess->state() == QProcess::NotRunning)
|
||||
{
|
||||
@@ -261,12 +263,12 @@ void ZjuConnectController::stop()
|
||||
}
|
||||
}
|
||||
|
||||
void ZjuConnectController::writeInput(const QByteArray &data)
|
||||
void ZjuConnectProcess::writeInput(const QByteArray &data)
|
||||
{
|
||||
zjuConnectProcess->write(data);
|
||||
}
|
||||
|
||||
ZjuConnectController::~ZjuConnectController()
|
||||
ZjuConnectProcess::~ZjuConnectProcess()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
43
infrastructure/coreprocess/zjuconnectprocess.h
Normal file
43
infrastructure/coreprocess/zjuconnectprocess.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef ZJUCONNECTPROCESS_H
|
||||
#define ZJUCONNECTPROCESS_H
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
#include "application/coreprocess.h"
|
||||
#include "infrastructure/coreprocess/coreoutputbuffer.h"
|
||||
|
||||
class ZjuConnectProcess : public CoreProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ZjuConnectProcess(QObject *parent = nullptr);
|
||||
|
||||
~ZjuConnectProcess() override;
|
||||
|
||||
void start(const ConnectionProfile &profile) override;
|
||||
|
||||
void stop() override;
|
||||
|
||||
void writeInput(const QByteArray &data) override;
|
||||
|
||||
private:
|
||||
QString copyCoreForAppImage(const QString &programPath);
|
||||
|
||||
void processOutput(CoreOutputBuffer &buffer, const QByteArray &data, bool flushPending = false);
|
||||
|
||||
void processOutputLines(const QList<QByteArray> &lines);
|
||||
|
||||
QProcess *zjuConnectProcess;
|
||||
|
||||
QTemporaryDir *tempDir = nullptr;
|
||||
|
||||
QString graphFile;
|
||||
|
||||
CoreOutputBuffer standardOutputBuffer;
|
||||
CoreOutputBuffer standardErrorBuffer;
|
||||
bool stopRequested = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // ZJUCONNECTPROCESS_H
|
||||
161
infrastructure/platform/autostart.cpp
Normal file
161
infrastructure/platform/autostart.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "autostart.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace
|
||||
{
|
||||
QString nativeApplicationPath()
|
||||
{
|
||||
return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
|
||||
}
|
||||
|
||||
QString macApplicationBundlePath()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
QDir directory(QCoreApplication::applicationDirPath());
|
||||
directory.cdUp();
|
||||
directory.cdUp();
|
||||
return directory.absolutePath();
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
QString macApplicationBundleName()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
return QFileInfo(macApplicationBundlePath()).baseName();
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void AutoStart::setEnabled(bool enabled)
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
QSettings settings(
|
||||
R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)",
|
||||
QSettings::NativeFormat
|
||||
);
|
||||
if (enabled)
|
||||
{
|
||||
settings.setValue(QApplication::applicationName(), nativeApplicationPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.remove(QApplication::applicationName());
|
||||
}
|
||||
settings.sync();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
{
|
||||
const QStringList arguments{
|
||||
"-e",
|
||||
"tell application \"System Events\" to delete login item \"" +
|
||||
macApplicationBundleName() + "\""
|
||||
};
|
||||
QProcess process;
|
||||
process.start("osascript", arguments);
|
||||
process.waitForFinished();
|
||||
const QString error = process.readAllStandardError();
|
||||
if (!enabled && process.error() != QProcess::UnknownError)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"取消开机自启动失败",
|
||||
"无法删除登录项:" + process.errorString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!enabled && process.exitCode() != 0)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项:" + error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (enabled)
|
||||
{
|
||||
const QStringList arguments{
|
||||
"-e",
|
||||
"tell application \"System Events\" to make login item at end with properties "
|
||||
"{path:\"" + macApplicationBundlePath() + "\", hidden:false}"
|
||||
};
|
||||
QProcess process;
|
||||
process.start("osascript", arguments);
|
||||
process.waitForFinished();
|
||||
if (process.error() != QProcess::UnknownError)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"设置开机自启动失败",
|
||||
"无法创建登录项:" + process.errorString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (process.exitCode() != 0)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"设置开机自启动失败",
|
||||
"无法创建登录项:" + process.readAllStandardError()
|
||||
);
|
||||
}
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
const QString directoryPath =
|
||||
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/autostart/";
|
||||
QDir directory(directoryPath);
|
||||
QFile desktopFile(directoryPath + QApplication::applicationName() + ".desktop");
|
||||
|
||||
if (directory.exists() && desktopFile.exists() && !desktopFile.remove())
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"取消开机自启动失败",
|
||||
"无法删除 .desktop 文件:" + desktopFile.fileName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!directory.exists() && !directory.mkpath("."))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"设置开机自启动失败",
|
||||
"无法创建 autostart 目录:" + directoryPath
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!desktopFile.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
nullptr,
|
||||
"设置开机自启动失败",
|
||||
"无法创建 .desktop 文件:" + desktopFile.fileName()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream output(&desktopFile);
|
||||
output << "[Desktop Entry]\n";
|
||||
output << "Type=Application\n";
|
||||
output << "Name=" << QApplication::applicationName() << "\n";
|
||||
output << "Exec=" << nativeApplicationPath() << "\n";
|
||||
output << "X-GNOME-Autostart-enabled=true\n";
|
||||
#else
|
||||
Q_UNUSED(enabled)
|
||||
#endif
|
||||
}
|
||||
9
infrastructure/platform/autostart.h
Normal file
9
infrastructure/platform/autostart.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#ifndef AUTOSTART_H
|
||||
#define AUTOSTART_H
|
||||
|
||||
namespace AutoStart
|
||||
{
|
||||
void setEnabled(bool enabled);
|
||||
}
|
||||
|
||||
#endif // AUTOSTART_H
|
||||
@@ -1,18 +1,18 @@
|
||||
#include "platformsystemproxybackend.h"
|
||||
|
||||
#include "utils/utils.h"
|
||||
#include "infrastructure/platform/systemproxyfunctions.h"
|
||||
|
||||
bool PlatformSystemProxyBackend::hasConflict(const SystemProxyConfig &config)
|
||||
{
|
||||
return Utils::isSystemProxySet(config.httpPort, config.socksPort);
|
||||
return PlatformSystemProxy::isSet(config.httpPort, config.socksPort);
|
||||
}
|
||||
|
||||
void PlatformSystemProxyBackend::apply(const SystemProxyConfig &config)
|
||||
{
|
||||
Utils::setSystemProxy(config.httpPort, config.socksPort, config.bypass);
|
||||
PlatformSystemProxy::set(config.httpPort, config.socksPort, config.bypass);
|
||||
}
|
||||
|
||||
void PlatformSystemProxyBackend::clear()
|
||||
{
|
||||
Utils::clearSystemProxy();
|
||||
PlatformSystemProxy::clear();
|
||||
}
|
||||
74
infrastructure/platform/privileges.cpp
Normal file
74
infrastructure/platform/privileges.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "privileges.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QStringList>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <shellapi.h>
|
||||
#include <windows.h>
|
||||
#elif defined(Q_OS_UNIX)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
bool Privileges::isElevated()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
BOOL isAdmin = FALSE;
|
||||
PSID adminGroup = nullptr;
|
||||
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
|
||||
|
||||
if (AllocateAndInitializeSid(
|
||||
&ntAuthority,
|
||||
2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
&adminGroup
|
||||
))
|
||||
{
|
||||
CheckTokenMembership(nullptr, adminGroup, &isAdmin);
|
||||
FreeSid(adminGroup);
|
||||
}
|
||||
return isAdmin;
|
||||
#elif defined(Q_OS_UNIX)
|
||||
return geteuid() == 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Privileges::relaunchElevated()
|
||||
{
|
||||
const QString program = QCoreApplication::applicationFilePath();
|
||||
QStringList arguments = QCoreApplication::arguments();
|
||||
if (!arguments.isEmpty())
|
||||
{
|
||||
arguments.removeFirst();
|
||||
}
|
||||
if (!arguments.contains("--connect"))
|
||||
{
|
||||
arguments << "--connect";
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
QStringList quotedArguments;
|
||||
for (const QString &argument : arguments)
|
||||
{
|
||||
quotedArguments << (argument.contains(' ') ? "\"" + argument + "\"" : argument);
|
||||
}
|
||||
const QString joinedArguments = quotedArguments.join(' ');
|
||||
const HINSTANCE result = ShellExecuteW(
|
||||
nullptr,
|
||||
L"runas",
|
||||
reinterpret_cast<LPCWSTR>(program.utf16()),
|
||||
reinterpret_cast<LPCWSTR>(joinedArguments.utf16()),
|
||||
nullptr,
|
||||
SW_SHOWNORMAL
|
||||
);
|
||||
return reinterpret_cast<INT_PTR>(result) > 32;
|
||||
#else
|
||||
Q_UNUSED(program)
|
||||
Q_UNUSED(arguments)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
10
infrastructure/platform/privileges.h
Normal file
10
infrastructure/platform/privileges.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef PRIVILEGES_H
|
||||
#define PRIVILEGES_H
|
||||
|
||||
namespace Privileges
|
||||
{
|
||||
bool isElevated();
|
||||
bool relaunchElevated();
|
||||
}
|
||||
|
||||
#endif // PRIVILEGES_H
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
#include "utils.h"
|
||||
#include "systemproxyfunctions.h"
|
||||
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
#include "windows.h"
|
||||
@@ -558,7 +558,7 @@ bool linuxIsSystemProxySet(int http_port, int socks_port)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Utils::isSystemProxySet(int http_port, int socks_port)
|
||||
bool PlatformSystemProxy::isSet(int http_port, int socks_port)
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
Q_UNUSED(socks_port)
|
||||
@@ -587,7 +587,7 @@ bool Utils::isSystemProxySet(int http_port, int socks_port)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Utils::setSystemProxy(int http_port, int socks_port, const QString &bypass)
|
||||
void PlatformSystemProxy::set(int http_port, int socks_port, const QString &bypass)
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
windowsSetProxyForAllConnections(
|
||||
@@ -608,7 +608,7 @@ void Utils::setSystemProxy(int http_port, int socks_port, const QString &bypass)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Utils::clearSystemProxy()
|
||||
void PlatformSystemProxy::clear()
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
windowsClearProxyForAllConnections();
|
||||
13
infrastructure/platform/systemproxyfunctions.h
Normal file
13
infrastructure/platform/systemproxyfunctions.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef SYSTEMPROXYFUNCTIONS_H
|
||||
#define SYSTEMPROXYFUNCTIONS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace PlatformSystemProxy
|
||||
{
|
||||
bool isSet(int httpPort = -1, int socksPort = -1);
|
||||
void set(int httpPort, int socksPort, const QString &bypass);
|
||||
void clear();
|
||||
}
|
||||
|
||||
#endif // SYSTEMPROXYFUNCTIONS_H
|
||||
@@ -7,9 +7,26 @@
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
|
||||
ProfileManager::ProfileManager()
|
||||
namespace
|
||||
{
|
||||
configRootPath = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||
bool createEmptyFile(const QString &path)
|
||||
{
|
||||
const QFileInfo fileInfo(path);
|
||||
if (fileInfo.exists())
|
||||
{
|
||||
return fileInfo.isFile();
|
||||
}
|
||||
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::WriteOnly | QIODevice::NewOnly);
|
||||
}
|
||||
}
|
||||
|
||||
ProfileManager::ProfileManager(const QString &storageRoot)
|
||||
{
|
||||
configRootPath = storageRoot.isEmpty()
|
||||
? QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)
|
||||
: storageRoot;
|
||||
profilesPath = QDir(configRootPath).filePath("profiles");
|
||||
statePath = QDir(configRootPath).filePath("state.ini");
|
||||
defaultProfilePath = QDir(configRootPath).filePath("config.ini");
|
||||
@@ -42,8 +59,7 @@ QString ProfileManager::activeProfile() const
|
||||
profileId = "";
|
||||
if (!QFileInfo::exists(profilePath(profileId)))
|
||||
{
|
||||
QSettings defaultSettings(profilePath(profileId), QSettings::IniFormat);
|
||||
defaultSettings.sync();
|
||||
createEmptyFile(profilePath(profileId));
|
||||
}
|
||||
state.setValue("Profile/Active", profileId);
|
||||
state.sync();
|
||||
@@ -53,18 +69,16 @@ QString ProfileManager::activeProfile() const
|
||||
|
||||
bool ProfileManager::setActiveProfile(const QString &profileId) const
|
||||
{
|
||||
QString normalizedId = normalizeProfileId(profileId);
|
||||
bool result = true;
|
||||
if (normalizedId.isEmpty() || !QFileInfo::exists(profilePath(normalizedId)))
|
||||
const QString normalizedId = normalizeProfileId(profileId);
|
||||
if (!QFileInfo::exists(profilePath(normalizedId)))
|
||||
{
|
||||
result = false;
|
||||
normalizedId = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
QSettings state(statePath, QSettings::IniFormat);
|
||||
state.setValue("Profile/Active", normalizedId);
|
||||
state.sync();
|
||||
return result;
|
||||
return state.status() == QSettings::NoError;
|
||||
}
|
||||
|
||||
QString ProfileManager::profilePath(const QString &profileId) const
|
||||
@@ -95,8 +109,10 @@ QString ProfileManager::createProfile(const QString &requestedName, const QStrin
|
||||
}
|
||||
else
|
||||
{
|
||||
QSettings newSettings(path, QSettings::IniFormat);
|
||||
newSettings.sync();
|
||||
if (!createEmptyFile(path))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return normalizedId;
|
||||
52
infrastructure/settings/profilemanager.h
Normal file
52
infrastructure/settings/profilemanager.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef PROFILEMANAGER_H
|
||||
#define PROFILEMANAGER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "application/profilebackend.h"
|
||||
|
||||
class ProfileManager : public ProfileBackend
|
||||
{
|
||||
public:
|
||||
explicit ProfileManager(const QString &storageRoot = {});
|
||||
|
||||
QStringList listProfiles() const override;
|
||||
|
||||
QString activeProfile() const override;
|
||||
|
||||
bool setActiveProfile(const QString &profileId) const override;
|
||||
|
||||
QString profilePath(const QString &profileId) const override;
|
||||
|
||||
QString createProfile(
|
||||
const QString &requestedName,
|
||||
const QString &sourcePath = QString()
|
||||
) const override;
|
||||
|
||||
bool renameProfile(const QString &oldId, const QString &newId) const override;
|
||||
|
||||
bool removeProfile(const QString &profileId) const override;
|
||||
|
||||
QString normalizeProfileId(const QString &name) const override;
|
||||
|
||||
bool autoStartEnabled() const;
|
||||
|
||||
void setAutoStartEnabled(bool enabled) const override;
|
||||
|
||||
bool silentStartEnabled() const override;
|
||||
|
||||
void setSilentStartEnabled(bool enabled) const;
|
||||
|
||||
private:
|
||||
QString configRootPath;
|
||||
QString profilesPath;
|
||||
QString statePath;
|
||||
QString defaultProfilePath;
|
||||
|
||||
QString ensureUniqueProfileId(const QString &baseId) const;
|
||||
|
||||
void ensureStorage() const;
|
||||
};
|
||||
|
||||
#endif //PROFILEMANAGER_H
|
||||
47
infrastructure/storage/applicationpaths.cpp
Normal file
47
infrastructure/storage/applicationpaths.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "applicationpaths.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
QString ApplicationPaths::clientDataFile(const QString &profileId)
|
||||
{
|
||||
QDir dataDirectory(
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
|
||||
);
|
||||
if (!dataDirectory.exists())
|
||||
{
|
||||
dataDirectory.mkpath(".");
|
||||
}
|
||||
|
||||
QDir profileDirectory(dataDirectory.filePath("profiles/" + profileId));
|
||||
if (!profileDirectory.exists())
|
||||
{
|
||||
profileDirectory.mkpath(".");
|
||||
}
|
||||
|
||||
QFile clientData(profileDirectory.filePath("client-data.json"));
|
||||
if (!clientData.exists() && clientData.open(QIODevice::WriteOnly))
|
||||
{
|
||||
clientData.write("{}");
|
||||
clientData.close();
|
||||
}
|
||||
return clientData.fileName();
|
||||
}
|
||||
|
||||
void ApplicationPaths::clearClientData(const QString &profileId)
|
||||
{
|
||||
QFile::remove(clientDataFile(profileId));
|
||||
}
|
||||
|
||||
QString ApplicationPaths::logFile()
|
||||
{
|
||||
QDir logDirectory(
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
|
||||
);
|
||||
if (!logDirectory.exists())
|
||||
{
|
||||
logDirectory.mkpath(".");
|
||||
}
|
||||
return logDirectory.filePath("zjuconnect.log");
|
||||
}
|
||||
13
infrastructure/storage/applicationpaths.h
Normal file
13
infrastructure/storage/applicationpaths.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef APPLICATIONPATHS_H
|
||||
#define APPLICATIONPATHS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace ApplicationPaths
|
||||
{
|
||||
QString clientDataFile(const QString &profileId);
|
||||
void clearClientData(const QString &profileId);
|
||||
QString logFile();
|
||||
}
|
||||
|
||||
#endif // APPLICATIONPATHS_H
|
||||
315
infrastructure/update/updatechecker.cpp
Normal file
315
infrastructure/update/updatechecker.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "infrastructure/update/updatechecker.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QStringView>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include "application/applicationconstants.h"
|
||||
#include "infrastructure/coreprocess/coreexecutable.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class VersionSuffixKind
|
||||
{
|
||||
Prerelease,
|
||||
Stable,
|
||||
Hotfix
|
||||
};
|
||||
|
||||
QString normalizedSuffix(const QString &version, qsizetype suffixIndex)
|
||||
{
|
||||
QString suffix = version.mid(suffixIndex);
|
||||
while (!suffix.isEmpty() && QStringLiteral("-+_.").contains(suffix.front()))
|
||||
{
|
||||
suffix.removeFirst();
|
||||
}
|
||||
return suffix;
|
||||
}
|
||||
|
||||
VersionSuffixKind suffixKind(const QString &suffix)
|
||||
{
|
||||
if (suffix.isEmpty())
|
||||
{
|
||||
return VersionSuffixKind::Stable;
|
||||
}
|
||||
if (suffix.startsWith(QStringLiteral("hotfix"), Qt::CaseInsensitive))
|
||||
{
|
||||
return VersionSuffixKind::Hotfix;
|
||||
}
|
||||
return VersionSuffixKind::Prerelease;
|
||||
}
|
||||
|
||||
int compareSuffixes(const QString ¤tSuffix, const QString &latestSuffix)
|
||||
{
|
||||
qsizetype currentIndex = 0;
|
||||
qsizetype latestIndex = 0;
|
||||
while (currentIndex < currentSuffix.size() &&
|
||||
latestIndex < latestSuffix.size())
|
||||
{
|
||||
const bool currentIsDigit = currentSuffix.at(currentIndex).isDigit();
|
||||
const bool latestIsDigit = latestSuffix.at(latestIndex).isDigit();
|
||||
if (currentIsDigit && latestIsDigit)
|
||||
{
|
||||
qsizetype currentEnd = currentIndex;
|
||||
qsizetype latestEnd = latestIndex;
|
||||
while (currentEnd < currentSuffix.size() &&
|
||||
currentSuffix.at(currentEnd).isDigit())
|
||||
{
|
||||
++currentEnd;
|
||||
}
|
||||
while (latestEnd < latestSuffix.size() &&
|
||||
latestSuffix.at(latestEnd).isDigit())
|
||||
{
|
||||
++latestEnd;
|
||||
}
|
||||
|
||||
qsizetype currentNumberStart = currentIndex;
|
||||
qsizetype latestNumberStart = latestIndex;
|
||||
while (currentNumberStart < currentEnd - 1 &&
|
||||
currentSuffix.at(currentNumberStart) == '0')
|
||||
{
|
||||
++currentNumberStart;
|
||||
}
|
||||
while (latestNumberStart < latestEnd - 1 &&
|
||||
latestSuffix.at(latestNumberStart) == '0')
|
||||
{
|
||||
++latestNumberStart;
|
||||
}
|
||||
|
||||
const qsizetype currentLength = currentEnd - currentNumberStart;
|
||||
const qsizetype latestLength = latestEnd - latestNumberStart;
|
||||
if (currentLength != latestLength)
|
||||
{
|
||||
return currentLength < latestLength ? -1 : 1;
|
||||
}
|
||||
|
||||
const QStringView currentNumber(currentSuffix);
|
||||
const QStringView latestNumber(latestSuffix);
|
||||
const int numberComparison =
|
||||
currentNumber.mid(currentNumberStart, currentLength).compare(
|
||||
latestNumber.mid(latestNumberStart, latestLength)
|
||||
);
|
||||
if (numberComparison != 0)
|
||||
{
|
||||
return numberComparison;
|
||||
}
|
||||
|
||||
currentIndex = currentEnd;
|
||||
latestIndex = latestEnd;
|
||||
continue;
|
||||
}
|
||||
|
||||
const QChar currentCharacter = currentSuffix.at(currentIndex).toLower();
|
||||
const QChar latestCharacter = latestSuffix.at(latestIndex).toLower();
|
||||
if (currentCharacter != latestCharacter)
|
||||
{
|
||||
return currentCharacter < latestCharacter ? -1 : 1;
|
||||
}
|
||||
++currentIndex;
|
||||
++latestIndex;
|
||||
}
|
||||
|
||||
if (currentIndex == currentSuffix.size() &&
|
||||
latestIndex == latestSuffix.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return currentIndex == currentSuffix.size() ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateChecker::UpdateChecker(QObject *parent)
|
||||
: QObject(parent),
|
||||
versions{
|
||||
QApplication::applicationVersion(),
|
||||
QStringLiteral("正在检查"),
|
||||
QStringLiteral("未知"),
|
||||
QStringLiteral("正在检查")
|
||||
},
|
||||
uiNetworkManager(new QNetworkAccessManager(this)),
|
||||
coreNetworkManager(new QNetworkAccessManager(this))
|
||||
{
|
||||
connect(uiNetworkManager, &QNetworkAccessManager::finished,
|
||||
this, &UpdateChecker::handleUiReply);
|
||||
connect(coreNetworkManager, &QNetworkAccessManager::finished,
|
||||
this, &UpdateChecker::handleCoreReply);
|
||||
}
|
||||
|
||||
const VersionInfo &UpdateChecker::versionInfo() const
|
||||
{
|
||||
return versions;
|
||||
}
|
||||
|
||||
void UpdateChecker::markDisabled()
|
||||
{
|
||||
versions.uiLatest = QStringLiteral("已禁用");
|
||||
versions.coreLatest = QStringLiteral("已禁用");
|
||||
emit versionInfoChanged(versions);
|
||||
}
|
||||
|
||||
void UpdateChecker::check()
|
||||
{
|
||||
try
|
||||
{
|
||||
versions.coreVersion = CoreExecutable::version(this);
|
||||
qInfo().noquote() << "检查核心版本成功:" + versions.coreVersion;
|
||||
}
|
||||
catch (const std::runtime_error &error)
|
||||
{
|
||||
qWarning().noquote() << "检查核心版本失败:" + QString(error.what());
|
||||
versions.coreVersion = QStringLiteral("错误");
|
||||
}
|
||||
emit versionInfoChanged(versions);
|
||||
|
||||
uiNetworkManager->get(QNetworkRequest(
|
||||
QUrl(
|
||||
"https://api.github.com/repos/" +
|
||||
ApplicationConstants::RepositoryName +
|
||||
"/releases/latest"
|
||||
)
|
||||
));
|
||||
coreNetworkManager->get(QNetworkRequest(
|
||||
QUrl(
|
||||
"https://api.github.com/repos/" +
|
||||
ApplicationConstants::CoreRepositoryName +
|
||||
"/releases/latest"
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
QString UpdateChecker::normalizeVersionTag(const QString &tag)
|
||||
{
|
||||
return tag.startsWith('v') ? tag.mid(1) : tag;
|
||||
}
|
||||
|
||||
std::optional<QString> UpdateChecker::parseReleaseVersion(
|
||||
const QByteArray &payload
|
||||
)
|
||||
{
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument document =
|
||||
QJsonDocument::fromJson(payload, &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !document.isObject())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonValue tagValue = document.object().value("tag_name");
|
||||
if (!tagValue.isString())
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString version =
|
||||
normalizeVersionTag(tagValue.toString().trimmed());
|
||||
return version.isEmpty()
|
||||
? std::nullopt
|
||||
: std::optional<QString>(version);
|
||||
}
|
||||
|
||||
bool UpdateChecker::isNewerVersion(const QString ¤tVersion, const QString &latestVersion)
|
||||
{
|
||||
qsizetype currentSuffix = 0;
|
||||
qsizetype latestSuffix = 0;
|
||||
const QVersionNumber current = QVersionNumber::fromString(currentVersion, ¤tSuffix);
|
||||
const QVersionNumber latest = QVersionNumber::fromString(latestVersion, &latestSuffix);
|
||||
|
||||
if (latest != current)
|
||||
{
|
||||
return latest > current;
|
||||
}
|
||||
|
||||
const QString currentSuffixText =
|
||||
normalizedSuffix(currentVersion, currentSuffix);
|
||||
const QString latestSuffixText =
|
||||
normalizedSuffix(latestVersion, latestSuffix);
|
||||
const VersionSuffixKind currentKind = suffixKind(currentSuffixText);
|
||||
const VersionSuffixKind latestKind = suffixKind(latestSuffixText);
|
||||
if (latestKind != currentKind)
|
||||
{
|
||||
return latestKind > currentKind;
|
||||
}
|
||||
return compareSuffixes(currentSuffixText, latestSuffixText) < 0;
|
||||
}
|
||||
|
||||
void UpdateChecker::handleUiReply(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
const QString reason = reply->errorString();
|
||||
qWarning().noquote() << "检查 UI 更新失败。原因是:" + reason;
|
||||
versions.uiLatest = QStringLiteral("检查失败");
|
||||
reply->deleteLater();
|
||||
emit checkFailed(UpdateComponent::Ui, reason);
|
||||
emit versionInfoChanged(versions);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<QString> latestVersion =
|
||||
parseReleaseVersion(reply->readAll());
|
||||
reply->deleteLater();
|
||||
if (!latestVersion.has_value())
|
||||
{
|
||||
const QString reason =
|
||||
QStringLiteral("响应中缺少有效的版本标签");
|
||||
qWarning().noquote() << "检查 UI 更新失败。原因是:" + reason;
|
||||
versions.uiLatest = QStringLiteral("检查失败");
|
||||
emit checkFailed(UpdateComponent::Ui, reason);
|
||||
emit versionInfoChanged(versions);
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo().noquote()
|
||||
<< "检查 UI 更新成功。最新版本:" + *latestVersion;
|
||||
versions.uiLatest = *latestVersion;
|
||||
emit versionInfoChanged(versions);
|
||||
|
||||
if (isNewerVersion(versions.uiVersion, *latestVersion))
|
||||
{
|
||||
emit uiUpdateAvailable(*latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateChecker::handleCoreReply(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
const QString reason = reply->errorString();
|
||||
qWarning().noquote() << "检查核心更新失败。原因是:" + reason;
|
||||
versions.coreLatest = QStringLiteral("检查失败");
|
||||
reply->deleteLater();
|
||||
emit checkFailed(UpdateComponent::Core, reason);
|
||||
emit versionInfoChanged(versions);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<QString> latestVersion =
|
||||
parseReleaseVersion(reply->readAll());
|
||||
reply->deleteLater();
|
||||
if (!latestVersion.has_value())
|
||||
{
|
||||
const QString reason =
|
||||
QStringLiteral("响应中缺少有效的版本标签");
|
||||
qWarning().noquote() << "检查核心更新失败。原因是:" + reason;
|
||||
versions.coreLatest = QStringLiteral("检查失败");
|
||||
emit checkFailed(UpdateComponent::Core, reason);
|
||||
emit versionInfoChanged(versions);
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo().noquote()
|
||||
<< "检查核心更新成功。最新版本:" + *latestVersion;
|
||||
versions.coreLatest = *latestVersion;
|
||||
emit versionInfoChanged(versions);
|
||||
|
||||
if (isNewerVersion(versions.coreVersion, *latestVersion))
|
||||
{
|
||||
qInfo().noquote() << "核心版本存在更新,可手动更新或通知开发者更新。";
|
||||
}
|
||||
}
|
||||
56
infrastructure/update/updatechecker.h
Normal file
56
infrastructure/update/updatechecker.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef UPDATECHECKER_H
|
||||
#define UPDATECHECKER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include <optional>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
struct VersionInfo
|
||||
{
|
||||
QString uiVersion;
|
||||
QString uiLatest;
|
||||
QString coreVersion;
|
||||
QString coreLatest;
|
||||
};
|
||||
|
||||
enum class UpdateComponent
|
||||
{
|
||||
Ui,
|
||||
Core
|
||||
};
|
||||
|
||||
class UpdateChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateChecker(QObject *parent = nullptr);
|
||||
|
||||
const VersionInfo &versionInfo() const;
|
||||
void markDisabled();
|
||||
void check();
|
||||
|
||||
static QString normalizeVersionTag(const QString &tag);
|
||||
static std::optional<QString> parseReleaseVersion(const QByteArray &payload);
|
||||
static bool isNewerVersion(const QString ¤tVersion, const QString &latestVersion);
|
||||
|
||||
signals:
|
||||
void versionInfoChanged(const VersionInfo &versionInfo);
|
||||
void checkFailed(UpdateComponent component, const QString &reason);
|
||||
void uiUpdateAvailable(const QString &version);
|
||||
|
||||
private:
|
||||
void handleUiReply(QNetworkReply *reply);
|
||||
void handleCoreReply(QNetworkReply *reply);
|
||||
|
||||
VersionInfo versions;
|
||||
QNetworkAccessManager *uiNetworkManager;
|
||||
QNetworkAccessManager *coreNetworkManager;
|
||||
};
|
||||
|
||||
#endif // UPDATECHECKER_H
|
||||
12
main.cpp
12
main.cpp
@@ -1,10 +1,14 @@
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QLibraryInfo>
|
||||
#include <QLocale>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "SingleApplication"
|
||||
|
||||
#include "application/applicationlogger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "utils/utils.h"
|
||||
#include "application/applicationconstants.h"
|
||||
#include "presentation/main/mainwindow.h"
|
||||
|
||||
#ifndef PROJ_VER
|
||||
#define PROJ_VER "unknown"
|
||||
@@ -13,8 +17,8 @@
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SingleApplication app(argc, argv, false, SingleApplication::Mode::System);
|
||||
QApplication::setApplicationName(Utils::APP_NAME);
|
||||
QApplication::setApplicationDisplayName(Utils::APP_NAME);
|
||||
QApplication::setApplicationName(ApplicationConstants::ApplicationName);
|
||||
QApplication::setApplicationDisplayName(ApplicationConstants::ApplicationName);
|
||||
QApplication::setApplicationVersion(PROJ_VER);
|
||||
QLocale::setDefault(QLocale(QLocale::Chinese, QLocale::SimplifiedChineseScript, QLocale::China));
|
||||
|
||||
|
||||
209
presentation/coordinators/authdialogcoordinator.cpp
Normal file
209
presentation/coordinators/authdialogcoordinator.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "authdialogcoordinator.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QSettings>
|
||||
#include <QUrlQuery>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "presentation/dialogs/graphcaptchawindow/graphcaptchawindow.h"
|
||||
#include "presentation/dialogs/loginwindow/loginwindow.h"
|
||||
#include "presentation/dialogs/ssologinwebview/ssologinwebview.h"
|
||||
#include "presentation/dialogs/sudowindow/sudowindow.h"
|
||||
|
||||
AuthDialogCoordinator::AuthDialogCoordinator(
|
||||
QWidget *parentWidget,
|
||||
QSettings *settings,
|
||||
QObject *parent
|
||||
)
|
||||
: QObject(parent),
|
||||
parentWidget(parentWidget),
|
||||
settings(settings)
|
||||
{
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::setSettings(QSettings *newSettings)
|
||||
{
|
||||
settings = newSettings;
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestLogin(
|
||||
const QString &username,
|
||||
const QString &password
|
||||
)
|
||||
{
|
||||
if (loginWindow != nullptr)
|
||||
{
|
||||
loginWindow->raise();
|
||||
loginWindow->activateWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
loginWindow = new LoginWindow(parentWidget);
|
||||
loginWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
loginWindow->setDetail(username, password);
|
||||
connect(loginWindow, &LoginWindow::login, this,
|
||||
&AuthDialogCoordinator::loginSubmitted);
|
||||
loginWindow->show();
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestSudoPassword()
|
||||
{
|
||||
if (sudoWindow != nullptr)
|
||||
{
|
||||
sudoWindow->raise();
|
||||
sudoWindow->activateWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
sudoWindow = new SudoWindow(parentWidget);
|
||||
sudoWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(sudoWindow, &SudoWindow::sudo, this,
|
||||
&AuthDialogCoordinator::sudoPasswordSubmitted);
|
||||
connect(sudoWindow, &QDialog::rejected, this,
|
||||
[this]()
|
||||
{
|
||||
emit sudoPasswordSubmitted({}, false);
|
||||
});
|
||||
sudoWindow->show();
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestGraphCaptcha(const QString &graphFile)
|
||||
{
|
||||
qInfo().noquote() << "需要图形验证码";
|
||||
if (graphCaptchaWindow != nullptr)
|
||||
{
|
||||
graphCaptchaWindow->setGraph(graphFile);
|
||||
graphCaptchaWindow->raise();
|
||||
graphCaptchaWindow->activateWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
graphCaptchaWindow = new GraphCaptchaWindow(parentWidget);
|
||||
graphCaptchaWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
graphCaptchaWindow->setGraph(graphFile);
|
||||
connect(graphCaptchaWindow, &GraphCaptchaWindow::finishCaptcha, this,
|
||||
[this](const QByteArray &captcha)
|
||||
{
|
||||
qInfo().noquote() << "图形验证码已提交";
|
||||
emit interactiveInputSubmitted(captcha + "\n");
|
||||
});
|
||||
graphCaptchaWindow->show();
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestSmsCode(bool showSkipSecondaryAuthOption)
|
||||
{
|
||||
qInfo().noquote() << "需要短信验证码";
|
||||
|
||||
QDialog dialog(parentWidget);
|
||||
dialog.setWindowTitle("短信验证码");
|
||||
|
||||
auto *layout = new QVBoxLayout(&dialog);
|
||||
layout->addWidget(new QLabel("请输入短信验证码:", &dialog));
|
||||
|
||||
auto *codeEdit = new QLineEdit(&dialog);
|
||||
layout->addWidget(codeEdit);
|
||||
|
||||
auto *skipCheckBox = new QCheckBox("跳过以后的短信验证", &dialog);
|
||||
skipCheckBox->setVisible(showSkipSecondaryAuthOption);
|
||||
layout->addWidget(skipCheckBox);
|
||||
|
||||
auto *buttonBox = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
||||
&dialog
|
||||
);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
const bool accepted = dialog.exec() == QDialog::Accepted;
|
||||
QByteArray input;
|
||||
if (accepted)
|
||||
{
|
||||
input = codeEdit->text().toLocal8Bit();
|
||||
if (showSkipSecondaryAuthOption && skipCheckBox->isChecked())
|
||||
{
|
||||
input.prepend('$');
|
||||
}
|
||||
}
|
||||
|
||||
qInfo().noquote() << (accepted ? "短信验证码已提交" : "短信验证码输入已取消");
|
||||
emit interactiveInputSubmitted(input + "\n");
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestTotpCode()
|
||||
{
|
||||
qInfo().noquote() << "需要 TOTP 验证码";
|
||||
bool accepted = false;
|
||||
const QString totp = QInputDialog::getText(
|
||||
parentWidget,
|
||||
"TOTP 验证码",
|
||||
"请输入 TOTP 验证码:",
|
||||
QLineEdit::Normal,
|
||||
"",
|
||||
&accepted
|
||||
);
|
||||
qInfo().noquote() << (accepted ? "TOTP 验证码已提交" : "TOTP 验证码输入已取消");
|
||||
emit interactiveInputSubmitted(totp.toLocal8Bit() + "\n");
|
||||
}
|
||||
|
||||
void AuthDialogCoordinator::requestSsoLogin()
|
||||
{
|
||||
if (settings == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ssoLoginWebView != nullptr)
|
||||
{
|
||||
ssoLoginWebView->raise();
|
||||
ssoLoginWebView->activateWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
const QString serverHost =
|
||||
settings->value("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn").toString();
|
||||
const int serverPort = settings->value("ZJUConnect/ServerPort", 443).toInt();
|
||||
QUrl serverUrl;
|
||||
serverUrl.setScheme("https");
|
||||
serverUrl.setHost(serverHost);
|
||||
if (serverPort != 443)
|
||||
{
|
||||
serverUrl.setPort(serverPort);
|
||||
}
|
||||
|
||||
QString ssoUrl = settings->value("ZJUConnect/LoginURL").toString();
|
||||
if (ssoUrl.isEmpty())
|
||||
{
|
||||
QUrl defaultSsoUrl = serverUrl;
|
||||
defaultSsoUrl.setPath("/passport/v1/public/casLogin");
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(
|
||||
"sfDomain",
|
||||
settings->value("ZJUConnect/LoginDomain").toString()
|
||||
);
|
||||
defaultSsoUrl.setQuery(query);
|
||||
ssoUrl = defaultSsoUrl.toString();
|
||||
}
|
||||
if (ssoUrl.startsWith('/'))
|
||||
{
|
||||
ssoUrl = serverUrl.resolved(QUrl(ssoUrl)).toString();
|
||||
}
|
||||
|
||||
qInfo().noquote() << QStringLiteral("单点登录:") + ssoUrl;
|
||||
ssoLoginWebView = new SsoLoginWebView(parentWidget);
|
||||
ssoLoginWebView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
ssoLoginWebView->setCallbackServerUrl(serverUrl);
|
||||
ssoLoginWebView->setInitialUrl(QUrl::fromUserInput(ssoUrl));
|
||||
connect(ssoLoginWebView, &SsoLoginWebView::loginCompleted, this,
|
||||
[this](const QString &url)
|
||||
{
|
||||
emit interactiveInputSubmitted(url.toLocal8Bit() + "\n");
|
||||
});
|
||||
ssoLoginWebView->show();
|
||||
}
|
||||
47
presentation/coordinators/authdialogcoordinator.h
Normal file
47
presentation/coordinators/authdialogcoordinator.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef AUTHDIALOGCOORDINATOR_H
|
||||
#define AUTHDIALOGCOORDINATOR_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class GraphCaptchaWindow;
|
||||
class LoginWindow;
|
||||
class QSettings;
|
||||
class SsoLoginWebView;
|
||||
class SudoWindow;
|
||||
class QWidget;
|
||||
|
||||
class AuthDialogCoordinator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AuthDialogCoordinator(
|
||||
QWidget *parentWidget,
|
||||
QSettings *settings,
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
void setSettings(QSettings *settings);
|
||||
void requestLogin(const QString &username, const QString &password);
|
||||
void requestSudoPassword();
|
||||
void requestGraphCaptcha(const QString &graphFile);
|
||||
void requestSmsCode(bool showSkipSecondaryAuthOption);
|
||||
void requestTotpCode();
|
||||
void requestSsoLogin();
|
||||
|
||||
signals:
|
||||
void loginSubmitted(const QString &username, const QString &password, bool saveDetails);
|
||||
void sudoPasswordSubmitted(const QString &password, bool remember);
|
||||
void interactiveInputSubmitted(const QByteArray &input);
|
||||
|
||||
private:
|
||||
QWidget *parentWidget;
|
||||
QSettings *settings;
|
||||
QPointer<LoginWindow> loginWindow;
|
||||
QPointer<SudoWindow> sudoWindow;
|
||||
QPointer<GraphCaptchaWindow> graphCaptchaWindow;
|
||||
QPointer<SsoLoginWebView> ssoLoginWebView;
|
||||
};
|
||||
|
||||
#endif // AUTHDIALOGCOORDINATOR_H
|
||||
345
presentation/coordinators/connectionuicontroller.cpp
Normal file
345
presentation/coordinators/connectionuicontroller.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "connectionuicontroller.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSettings>
|
||||
#include <QWidget>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "application/applicationlogger.h"
|
||||
#include "application/connectionsession.h"
|
||||
#include "application/systemproxysession.h"
|
||||
#include "infrastructure/coreprocess/coreexecutable.h"
|
||||
#include "infrastructure/logging/corelogfile.h"
|
||||
#include "infrastructure/platform/privileges.h"
|
||||
#include "infrastructure/settings/settingsprofileloader.h"
|
||||
#include "presentation/coordinators/authdialogcoordinator.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(
|
||||
QWidget *parentWidget,
|
||||
QPushButton *connectButton,
|
||||
QPushButton *proxyButton,
|
||||
QAction *trayConnectAction,
|
||||
ConnectionSession *connectionSession,
|
||||
SystemProxySession *systemProxySession,
|
||||
AuthDialogCoordinator *authenticationDialogs,
|
||||
ApplicationLogger *applicationLogger,
|
||||
CoreLogFile *coreLogFile,
|
||||
SettingsProvider settingsProvider,
|
||||
ProfileIdProvider profileIdProvider,
|
||||
NotificationHandler notificationHandler,
|
||||
QObject *parent
|
||||
)
|
||||
: QObject(parent),
|
||||
parentWidget(parentWidget),
|
||||
connectButton(connectButton),
|
||||
proxyButton(proxyButton),
|
||||
trayConnectAction(trayConnectAction),
|
||||
connectionSession(connectionSession),
|
||||
systemProxySession(systemProxySession),
|
||||
authenticationDialogs(authenticationDialogs),
|
||||
settingsProvider(std::move(settingsProvider)),
|
||||
profileIdProvider(std::move(profileIdProvider)),
|
||||
notificationHandler(std::move(notificationHandler))
|
||||
{
|
||||
connectButton->setText("连接服务器");
|
||||
trayConnectAction->setText("连接服务器");
|
||||
proxyButton->setText("设置系统代理");
|
||||
proxyButton->hide();
|
||||
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::outputRead,
|
||||
applicationLogger,
|
||||
&ApplicationLogger::appendCoreOutput
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::outputRead,
|
||||
coreLogFile,
|
||||
&CoreLogFile::appendOutput
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::savedSudoPasswordRejected,
|
||||
this,
|
||||
[]() { qWarning().noquote() << "sudo 密码可能有误,不使用记住的密码"; }
|
||||
);
|
||||
connect(
|
||||
authenticationDialogs,
|
||||
&AuthDialogCoordinator::loginSubmitted,
|
||||
this,
|
||||
[this](const QString &username, const QString &password, bool saveDetails)
|
||||
{
|
||||
if (saveDetails)
|
||||
{
|
||||
settings()->setValue("Credential/Username", username);
|
||||
settings()->setValue(
|
||||
"Credential/Password",
|
||||
QString(password.toUtf8().toBase64())
|
||||
);
|
||||
settings()->sync();
|
||||
}
|
||||
startConnection(username, password);
|
||||
}
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::reconnectScheduled,
|
||||
this,
|
||||
[](int) { qInfo().noquote() << "正在尝试重新连接..."; }
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::finished,
|
||||
this,
|
||||
[this](ZJU_ERROR error)
|
||||
{
|
||||
qInfo().noquote() << "VPN 断开!";
|
||||
if (error != ZJU_ERROR::NONE)
|
||||
{
|
||||
this->notificationHandler(
|
||||
"VPN",
|
||||
"VPN 意外断开!",
|
||||
QSystemTrayIcon::MessageIcon::Warning
|
||||
);
|
||||
}
|
||||
this->connectButton->setText("连接服务器");
|
||||
this->trayConnectAction->setText("连接服务器");
|
||||
this->proxyButton->hide();
|
||||
showConnectionError(error);
|
||||
}
|
||||
);
|
||||
connect(
|
||||
connectButton,
|
||||
&QPushButton::clicked,
|
||||
this,
|
||||
&ConnectionUiController::handleConnectClicked
|
||||
);
|
||||
connect(
|
||||
proxyButton,
|
||||
&QPushButton::clicked,
|
||||
this,
|
||||
&ConnectionUiController::handleProxyClicked
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
QSettings *ConnectionUiController::settings() const
|
||||
{
|
||||
return settingsProvider();
|
||||
}
|
||||
|
||||
void ConnectionUiController::handleConnectClicked()
|
||||
{
|
||||
if (connectionSession->isActive())
|
||||
{
|
||||
connectionSession->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings()->contains("ZJUConnect/ServerAddress") &&
|
||||
settings()->value("ZJUConnect/ServerAddress").toString().isEmpty())
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "错误", "服务器地址不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString username = settings()->value("Credential/Username", "").toString();
|
||||
const QString password = QByteArray::fromBase64(
|
||||
settings()->value("Credential/Password", "").toString().toUtf8()
|
||||
);
|
||||
const QString protocol =
|
||||
settings()->value("ZJUConnect/Protocol", "easyconnect").toString();
|
||||
const QString authType =
|
||||
settings()->value("ZJUConnect/AuthType", "psw").toString();
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (settings()->value("ZJUConnect/TUNMode").toBool() &&
|
||||
!Privileges::isElevated())
|
||||
{
|
||||
if (Privileges::relaunchElevated())
|
||||
{
|
||||
QApplication::quit();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(
|
||||
parentWidget,
|
||||
"提升失败",
|
||||
"无法以管理员权限重新启动,请手动以管理员方式运行。"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
const bool passwordLogin =
|
||||
(protocol == "atrust" && authType == "psw") ||
|
||||
(protocol == "easyconnect" &&
|
||||
settings()->value("Credential/CertFile", "").toString().isEmpty());
|
||||
if (passwordLogin && (username.isEmpty() || password.isEmpty()))
|
||||
{
|
||||
authenticationDialogs->requestLogin(username, password);
|
||||
return;
|
||||
}
|
||||
startConnection(username, password);
|
||||
}
|
||||
|
||||
void ConnectionUiController::handleProxyClicked()
|
||||
{
|
||||
if (systemProxySession->isBusy())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (systemProxySession->isEnabled())
|
||||
{
|
||||
systemProxySession->disable();
|
||||
return;
|
||||
}
|
||||
|
||||
const int httpPort = settings()->value("ZJUConnect/HTTPPort").toInt();
|
||||
const int socksPort = settings()->value("ZJUConnect/SOCKS5Port").toInt();
|
||||
const SystemProxyConfig proxyConfig{
|
||||
httpPort,
|
||||
socksPort,
|
||||
settings()->value("Common/SystemProxyBypass").toString()
|
||||
};
|
||||
connect(
|
||||
systemProxySession,
|
||||
&SystemProxySession::conflictCheckFinished,
|
||||
this,
|
||||
[this, proxyConfig, httpPort, socksPort](bool conflict)
|
||||
{
|
||||
if (!connectionSession->isActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (conflict &&
|
||||
!settings()->value(
|
||||
"Common/SuppressProxyOverrideWarning",
|
||||
false
|
||||
).toBool())
|
||||
{
|
||||
QMessageBox messageBox(
|
||||
QMessageBox::Warning,
|
||||
"警告",
|
||||
"当前已存在系统代理配置(可能是 Clash 或其它代理软件)\n"
|
||||
"是否覆盖当前系统代理配置?",
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
parentWidget
|
||||
);
|
||||
auto *dontShowCheckBox = new QCheckBox("不再提示");
|
||||
messageBox.setCheckBox(dontShowCheckBox);
|
||||
if (messageBox.exec() == QMessageBox::No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (dontShowCheckBox->isChecked())
|
||||
{
|
||||
settings()->setValue(
|
||||
"Common/SuppressProxyOverrideWarning",
|
||||
true
|
||||
);
|
||||
settings()->sync();
|
||||
}
|
||||
}
|
||||
else if (conflict)
|
||||
{
|
||||
qInfo().noquote() << "跳过系统代理覆盖警告,因为已设置了不再提示";
|
||||
}
|
||||
|
||||
qInfo().noquote()
|
||||
<< "设置系统代理:HTTP端口 " + QString::number(httpPort)
|
||||
+ ",SOCKS5 端口 " + QString::number(socksPort);
|
||||
systemProxySession->enable(proxyConfig);
|
||||
},
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
systemProxySession->checkConflict(proxyConfig);
|
||||
}
|
||||
|
||||
void ConnectionUiController::startConnection(
|
||||
const QString &username,
|
||||
const QString &password
|
||||
)
|
||||
{
|
||||
ConnectionProfile profile = SettingsProfileLoader::load(
|
||||
*settings(),
|
||||
profileIdProvider(),
|
||||
username,
|
||||
password
|
||||
);
|
||||
profile.program = CoreExecutable::path();
|
||||
const ReconnectPolicy reconnectPolicy{
|
||||
settings()->value("Common/AutoReconnect", false).toBool(),
|
||||
settings()->value("Common/ReconnectTime", 1).toInt() * 1000
|
||||
};
|
||||
if (!connectionSession->start(profile, reconnectPolicy)
|
||||
|| !connectionSession->isActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
connectButton->setText("断开服务器");
|
||||
trayConnectAction->setText("断开服务器");
|
||||
proxyButton->show();
|
||||
|
||||
if (settings()->value("Common/AutoSetProxy", false).toBool())
|
||||
{
|
||||
proxyButton->click();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::showConnectionError(ZJU_ERROR error)
|
||||
{
|
||||
QString message;
|
||||
switch (error)
|
||||
{
|
||||
case ZJU_ERROR::INVALID_DETAIL:
|
||||
message = "登录失败!\n请检查设置中的网络账号和密码是否设置正确。";
|
||||
break;
|
||||
case ZJU_ERROR::BRUTE_FORCE:
|
||||
message = "登录失败!\n登录尝试过于频繁,IP 被风控,请稍后重试或换用 EasyConnect。";
|
||||
break;
|
||||
case ZJU_ERROR::OTHER_LOGIN_FAILED:
|
||||
message = "登录失败!\n未知原因,可将日志反馈给开发者以便调查。";
|
||||
break;
|
||||
case ZJU_ERROR::ACCESS_DENIED:
|
||||
message = "权限不足!\n请关闭程序,点击右键以管理员身份运行。";
|
||||
break;
|
||||
case ZJU_ERROR::LISTEN_FAILED:
|
||||
message = "监听失败!\n请关闭占用端口的程序(如残留的 zju-connect.exe),或者监听其它端口。";
|
||||
break;
|
||||
case ZJU_ERROR::CLIENT_FAILED:
|
||||
message = "连接失败!\n可能是响应超时,请检查本地网络配置是否正常,服务器设置是否正确。";
|
||||
break;
|
||||
case ZJU_ERROR::CAPTCHA_FAILED:
|
||||
message = "登录失败!\n验证码问题,可能是已验证码过期或者有误。";
|
||||
break;
|
||||
case ZJU_ERROR::PROGRAM_NOT_FOUND:
|
||||
message = "程序未找到!\n请检查核心是否在正确路径下,检查是否解压在当前目录下。";
|
||||
break;
|
||||
case ZJU_ERROR::INTERACTIVE_ERROR:
|
||||
message = "登录失败!\n请检查您的输入是否正确,检查是否完成 SSO 登录。";
|
||||
break;
|
||||
case ZJU_ERROR::AUTH_NOT_AVAILABLE:
|
||||
message = "认证方式/登录域不可用!\n请通过“获取认证方式”按钮配置认证方式。";
|
||||
break;
|
||||
case ZJU_ERROR::AUTH_EXPIRED:
|
||||
message = "认证已过期!\n请重新登录。";
|
||||
break;
|
||||
case ZJU_ERROR::OTHER:
|
||||
message = "其它错误!\n未知原因,可将日志反馈给开发者以便调查。";
|
||||
break;
|
||||
case ZJU_ERROR::NONE:
|
||||
return;
|
||||
}
|
||||
QMessageBox::critical(parentWidget, "错误", message);
|
||||
}
|
||||
69
presentation/coordinators/connectionuicontroller.h
Normal file
69
presentation/coordinators/connectionuicontroller.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef CONNECTIONUICONTROLLER_H
|
||||
#define CONNECTIONUICONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "core/connectionerror.h"
|
||||
|
||||
class QAction;
|
||||
class ApplicationLogger;
|
||||
class AuthDialogCoordinator;
|
||||
class ConnectionSession;
|
||||
class CoreLogFile;
|
||||
class QPushButton;
|
||||
class QSettings;
|
||||
class SystemProxySession;
|
||||
class QWidget;
|
||||
|
||||
class ConnectionUiController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using SettingsProvider = std::function<QSettings *()>;
|
||||
using ProfileIdProvider = std::function<QString()>;
|
||||
using NotificationHandler = std::function<void(
|
||||
const QString &title,
|
||||
const QString &content,
|
||||
QSystemTrayIcon::MessageIcon icon
|
||||
)>;
|
||||
|
||||
ConnectionUiController(
|
||||
QWidget *parentWidget,
|
||||
QPushButton *connectButton,
|
||||
QPushButton *proxyButton,
|
||||
QAction *trayConnectAction,
|
||||
ConnectionSession *connectionSession,
|
||||
SystemProxySession *systemProxySession,
|
||||
AuthDialogCoordinator *authenticationDialogs,
|
||||
ApplicationLogger *applicationLogger,
|
||||
CoreLogFile *coreLogFile,
|
||||
SettingsProvider settingsProvider,
|
||||
ProfileIdProvider profileIdProvider,
|
||||
NotificationHandler notificationHandler,
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
private:
|
||||
void handleConnectClicked();
|
||||
void handleProxyClicked();
|
||||
void startConnection(const QString &username, const QString &password);
|
||||
void showConnectionError(ZJU_ERROR error);
|
||||
QSettings *settings() const;
|
||||
|
||||
QWidget *parentWidget;
|
||||
QPushButton *connectButton;
|
||||
QPushButton *proxyButton;
|
||||
QAction *trayConnectAction;
|
||||
ConnectionSession *connectionSession;
|
||||
SystemProxySession *systemProxySession;
|
||||
AuthDialogCoordinator *authenticationDialogs;
|
||||
SettingsProvider settingsProvider;
|
||||
ProfileIdProvider profileIdProvider;
|
||||
NotificationHandler notificationHandler;
|
||||
};
|
||||
|
||||
#endif // CONNECTIONUICONTROLLER_H
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "authinfowindow.h"
|
||||
|
||||
#include "utils/utils.h"
|
||||
#include "infrastructure/coreprocess/consoleoutputdecoder.h"
|
||||
#include "infrastructure/coreprocess/coreexecutable.h"
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
@@ -36,7 +39,7 @@ AuthInfoWindow::AuthInfoWindow(QWidget *parent)
|
||||
[this]() { stdoutBuf_ += proc_->readAllStandardError(); });
|
||||
connect(proc_, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
|
||||
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
QString output = Utils::consoleOutputToQString(stdoutBuf_);
|
||||
QString output = ConsoleOutputDecoder::decode(stdoutBuf_);
|
||||
qInfo().noquote() << "可用认证方式:\n" + output;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8(), &jsonError);
|
||||
@@ -75,7 +78,7 @@ void AuthInfoWindow::fetchAuthInfo(const QString& serverAddress, int port)
|
||||
{
|
||||
stdoutBuf_.clear();
|
||||
stderrBuf_.clear();
|
||||
proc_->start(Utils::getCorePath(),
|
||||
proc_->start(CoreExecutable::path(),
|
||||
{"-protocol", "atrust", "-server", serverAddress, "-port", QString::number(port), "-auth-info"});
|
||||
qInfo().noquote() << "正在获取可用认证的方式...";
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
<customwidget>
|
||||
<class>CaptchaCanvas</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>graphcaptchawindow/captchacanvas.h</header>
|
||||
<header>presentation/dialogs/graphcaptchawindow/captchacanvas.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "loginwindow.h"
|
||||
|
||||
#include "utils/utils.h"
|
||||
#include "presentation/presentationhelpers.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
@@ -18,7 +18,10 @@ LoginWindow::LoginWindow(QWidget *parent)
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked,
|
||||
[&]()
|
||||
{
|
||||
if (!Utils::credentialCheck(ui->usernameLineEdit->text(), ui->passwordLineEdit->text()))
|
||||
if (!PresentationHelpers::confirmCredentials(
|
||||
ui->usernameLineEdit->text(),
|
||||
ui->passwordLineEdit->text()
|
||||
))
|
||||
return;
|
||||
emit login(ui->usernameLineEdit->text(), ui->passwordLineEdit->text(), ui->saveLoginDetailCheckBox->isChecked());
|
||||
emit accept();
|
||||
@@ -53,4 +56,3 @@ void LoginWindow::keyPressEvent(QKeyEvent *event)
|
||||
else
|
||||
QDialog::keyPressEvent(event);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
|
||||
#include "settingwindow.h"
|
||||
#include "ui_settingwindow.h"
|
||||
#include "utils/utils.h"
|
||||
#include "utils/profilemanager.h"
|
||||
#include "application/applicationconstants.h"
|
||||
#include "application/defaultsettings.h"
|
||||
#include "infrastructure/platform/autostart.h"
|
||||
#include "infrastructure/storage/applicationpaths.h"
|
||||
#include "presentation/presentationhelpers.h"
|
||||
#include "infrastructure/settings/profilemanager.h"
|
||||
|
||||
SettingWindow::SettingWindow(QWidget *parent, QSettings *inputSettings, const QString &profileId) :
|
||||
QDialog(parent),
|
||||
@@ -44,19 +48,25 @@ SettingWindow::SettingWindow(QWidget *parent, QSettings *inputSettings, const QS
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [&]() {
|
||||
if (shouldCheckCredential() && !Utils::credentialCheck(ui->usernameLineEdit->text(), ui->passwordLineEdit->text()))
|
||||
if (shouldCheckCredential() && !PresentationHelpers::confirmCredentials(
|
||||
ui->usernameLineEdit->text(),
|
||||
ui->passwordLineEdit->text()
|
||||
))
|
||||
return;
|
||||
if (isAuthSettingChanged())
|
||||
Utils::clearClientData(this->profileId);
|
||||
ApplicationPaths::clearClientData(this->profileId);
|
||||
applySettings();
|
||||
accept();
|
||||
});
|
||||
|
||||
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, [&]() {
|
||||
if (shouldCheckCredential() && !Utils::credentialCheck(ui->usernameLineEdit->text(), ui->passwordLineEdit->text()))
|
||||
if (shouldCheckCredential() && !PresentationHelpers::confirmCredentials(
|
||||
ui->usernameLineEdit->text(),
|
||||
ui->passwordLineEdit->text()
|
||||
))
|
||||
return;
|
||||
if (isAuthSettingChanged())
|
||||
Utils::clearClientData(this->profileId);
|
||||
ApplicationPaths::clearClientData(this->profileId);
|
||||
applySettings();
|
||||
loadSettings();
|
||||
});
|
||||
@@ -68,7 +78,7 @@ SettingWindow::SettingWindow(QWidget *parent, QSettings *inputSettings, const QS
|
||||
if (status == QMessageBox::Ok)
|
||||
{
|
||||
settings->clear();
|
||||
Utils::resetDefaultSettings(*settings);
|
||||
DefaultSettings::reset(*settings);
|
||||
settings->sync();
|
||||
loadSettings();
|
||||
}
|
||||
@@ -185,7 +195,8 @@ void SettingWindow::loadSettings()
|
||||
{
|
||||
ui->configVersionLabel->setText(
|
||||
"当前配置文件版本:" + QString::number(settings->value("Common/ConfigVersion").toInt()) +
|
||||
"\n程序配置文件版本:" + QString::number(Utils::CONFIG_VERSION)
|
||||
"\n程序配置文件版本:" +
|
||||
QString::number(ApplicationConstants::ConfigVersion)
|
||||
);
|
||||
ui->usernameLineEdit->setText(settings->value("Credential/Username").toString());
|
||||
ui->passwordLineEdit->setText(
|
||||
@@ -280,7 +291,7 @@ void SettingWindow::applySettings()
|
||||
bool oldAutoStart = profileManager.autoStartEnabled();
|
||||
bool newAutoStart = ui->autoStartCheckBox->isChecked();
|
||||
if (oldAutoStart != newAutoStart)
|
||||
Utils::setAutoStart(ui->autoStartCheckBox->isChecked());
|
||||
AutoStart::setEnabled(ui->autoStartCheckBox->isChecked());
|
||||
profileManager.setAutoStartEnabled(newAutoStart);
|
||||
profileManager.setSilentStartEnabled(ui->silentStartCheckBox->isChecked());
|
||||
|
||||
@@ -355,7 +366,10 @@ void SettingWindow::applySettings()
|
||||
settings->setValue("ZJUConnect/CustomProxyDomain", customProxyDomain);
|
||||
settings->setValue("ZJUConnect/ExtraArguments", extraArguments);
|
||||
|
||||
settings->setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
|
||||
settings->setValue(
|
||||
"Common/ConfigVersion",
|
||||
ApplicationConstants::ConfigVersion
|
||||
);
|
||||
|
||||
settings->sync();
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <QDialog>
|
||||
#include <QSettings>
|
||||
#include "ui_settingwindow.h"
|
||||
#include "extrasettingwindow/extrasettingwindow.h"
|
||||
#include "authinfowindow/authinfowindow.h"
|
||||
#include "presentation/dialogs/extrasettingwindow/extrasettingwindow.h"
|
||||
#include "presentation/dialogs/authinfowindow/authinfowindow.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@@ -55,7 +55,7 @@ void SsoLoginWebView::setupConnections()
|
||||
connect(ui->webEngineView->page(), &QWebEnginePage::navigationRequested, this,
|
||||
[&](QWebEngineNavigationRequest &request) {
|
||||
if (request.navigationType() == QWebEngineNavigationRequest::NavigationType::RedirectNavigation &&
|
||||
request.url().host() == callbackServerHost)
|
||||
isCallbackUrl(request.url()))
|
||||
{
|
||||
if (!loginCompletedEmitted)
|
||||
{
|
||||
@@ -79,9 +79,28 @@ void SsoLoginWebView::setInitialUrl(const QUrl &url)
|
||||
}
|
||||
}
|
||||
|
||||
void SsoLoginWebView::setCallbackServerHost(const QString &host)
|
||||
void SsoLoginWebView::setCallbackServerUrl(const QUrl &url)
|
||||
{
|
||||
callbackServerHost = host;
|
||||
callbackServerUrl = url;
|
||||
}
|
||||
|
||||
bool SsoLoginWebView::isCallbackUrl(const QUrl &url) const
|
||||
{
|
||||
const auto effectivePort = [](const QUrl &value)
|
||||
{
|
||||
const int defaultPort =
|
||||
value.scheme().compare("https", Qt::CaseInsensitive) == 0 ? 443 : 80;
|
||||
return value.port(defaultPort);
|
||||
};
|
||||
return url.scheme().compare(
|
||||
callbackServerUrl.scheme(),
|
||||
Qt::CaseInsensitive
|
||||
) == 0
|
||||
&& url.host().compare(
|
||||
callbackServerUrl.host(),
|
||||
Qt::CaseInsensitive
|
||||
) == 0
|
||||
&& effectivePort(url) == effectivePort(callbackServerUrl);
|
||||
}
|
||||
|
||||
QUrl SsoLoginWebView::currentUrl() const
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
~SsoLoginWebView() override;
|
||||
|
||||
void setInitialUrl(const QUrl &url);
|
||||
void setCallbackServerHost(const QString &host);
|
||||
void setCallbackServerUrl(const QUrl &url);
|
||||
QUrl currentUrl() const;
|
||||
|
||||
signals:
|
||||
@@ -31,9 +31,10 @@ protected:
|
||||
|
||||
private:
|
||||
void setupConnections();
|
||||
bool isCallbackUrl(const QUrl &url) const;
|
||||
|
||||
Ui::SsoLoginWebView *ui;
|
||||
QString callbackServerHost;
|
||||
QUrl callbackServerUrl;
|
||||
bool loginCompletedEmitted = false;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include <QMessageBox>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSysInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QClipboard>
|
||||
@@ -14,42 +12,76 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "application/applicationlogger.h"
|
||||
#include "infrastructure/corelogfile.h"
|
||||
#include "application/applicationconstants.h"
|
||||
#include "application/commandlineoptions.h"
|
||||
#include "application/settingsmigrator.h"
|
||||
#include "infrastructure/coreprocess/devicetrust.h"
|
||||
#include "infrastructure/logging/corelogfile.h"
|
||||
#include "infrastructure/storage/applicationpaths.h"
|
||||
#include "infrastructure/update/updatechecker.h"
|
||||
#include "presentation/coordinators/connectionuicontroller.h"
|
||||
#include "presentation/presentationhelpers.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "utils/utils.h"
|
||||
#include "zjuconnectcontroller/zjuconnectcontroller.h"
|
||||
|
||||
MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
applicationLogger(logger)
|
||||
{
|
||||
profileManager = new ProfileManager();
|
||||
const QString overrideConfigPath = Utils::getArgValue(QCoreApplication::arguments(), "--config-path");
|
||||
if (overrideConfigPath.isEmpty())
|
||||
{
|
||||
profileManager = new ProfileManager();
|
||||
currentProfileId = profileManager->activeProfile();
|
||||
settings = new QSettings(profileManager->profilePath(currentProfileId), QSettings::IniFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentProfileId = "custom";
|
||||
settings = new QSettings(overrideConfigPath, QSettings::IniFormat);
|
||||
}
|
||||
const QString overrideConfigPath =
|
||||
CommandLineOptions::value(QCoreApplication::arguments(), "--config-path");
|
||||
coordinator = new MainWindowCoordinator(this, overrideConfigPath, this);
|
||||
profileService = coordinator->profiles();
|
||||
currentProfileId = profileService->currentProfileId();
|
||||
settings = profileService->settings();
|
||||
|
||||
upgradeSettings();
|
||||
|
||||
isFirstTimeSetMode = true;
|
||||
|
||||
ui->setupUi(this);
|
||||
coreLogFile = new CoreLogFile(Utils::getLogFilePath(), this);
|
||||
authenticationDialogs = coordinator->authenticationDialogs();
|
||||
connectionSession = coordinator->connection();
|
||||
systemProxySession = coordinator->systemProxy();
|
||||
coreLogFile = new CoreLogFile(ApplicationPaths::logFile(), this);
|
||||
updateChecker = coordinator->updates();
|
||||
connect(updateChecker, &UpdateChecker::versionInfoChanged, this,
|
||||
[this](const VersionInfo &) { updateVersionInfo(); });
|
||||
connect(updateChecker, &UpdateChecker::checkFailed, this,
|
||||
[this](UpdateComponent component, const QString &)
|
||||
{
|
||||
const QString componentName =
|
||||
component == UpdateComponent::Ui ? QStringLiteral("UI") : QStringLiteral("核心");
|
||||
ui->versionLabel->setText(
|
||||
"当前版本:" + QApplication::applicationVersion() +
|
||||
"\n检查" + componentName + "更新失败\n"
|
||||
);
|
||||
});
|
||||
connect(updateChecker, &UpdateChecker::uiUpdateAvailable, this,
|
||||
[this](const QString &latestVersion)
|
||||
{
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setText("UI 版本更新");
|
||||
msgBox.setInformativeText(
|
||||
"存在 UI 版本更新:" + latestVersion + "\n是否前往 Github 发布页面查看?"
|
||||
);
|
||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Ok);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Ok)
|
||||
{
|
||||
QDesktopServices::openUrl(
|
||||
QUrl(
|
||||
"https://github.com/" +
|
||||
ApplicationConstants::RepositoryName +
|
||||
"/releases/latest"
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
connect(applicationLogger, &ApplicationLogger::entryAdded, this,
|
||||
[this](const QString &entry)
|
||||
{
|
||||
ui->logPlainTextEdit->appendPlainText(entry);
|
||||
});
|
||||
systemProxySession = new SystemProxySession(this);
|
||||
connect(systemProxySession, &SystemProxySession::busyChanged, this,
|
||||
[this](bool busy)
|
||||
{
|
||||
@@ -65,14 +97,6 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
ui->pushButton2->hide();
|
||||
}
|
||||
});
|
||||
connect(systemProxySession, &SystemProxySession::operationFinished, this,
|
||||
[this](bool enabled)
|
||||
{
|
||||
if (enabled && connectionSession != nullptr && !connectionSession->isActive())
|
||||
{
|
||||
systemProxySession->disable();
|
||||
}
|
||||
});
|
||||
setupTrayIcon();
|
||||
setupProfileMenu();
|
||||
|
||||
@@ -82,10 +106,6 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
|
||||
ui->applicationNameLabel->setText(QApplication::applicationDisplayName());
|
||||
|
||||
versionInfo.ui_version = QApplication::applicationVersion();
|
||||
versionInfo.ui_latest = "正在检查";
|
||||
versionInfo.core_version = "未知";
|
||||
versionInfo.core_latest = "正在检查";
|
||||
updateVersionInfo();
|
||||
|
||||
|
||||
@@ -163,7 +183,7 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::clearClientData(currentProfileId);
|
||||
ApplicationPaths::clearClientData(currentProfileId);
|
||||
qInfo().noquote() << "已清理登录缓存";
|
||||
});
|
||||
|
||||
@@ -173,7 +193,7 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils::setDeviceTrust(this,
|
||||
DeviceTrust::set(this,
|
||||
settings->value("ZJUConnect/Protocol", "easyconnect").toString(),
|
||||
settings->value("ZJUConnect/ServerAddress").toString(),
|
||||
settings->value("ZJUConnect/ServerPort").toInt(),
|
||||
@@ -194,7 +214,7 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
{
|
||||
try
|
||||
{
|
||||
Utils::setDeviceTrust(this,
|
||||
DeviceTrust::set(this,
|
||||
settings->value("ZJUConnect/Protocol", "easyconnect").toString(),
|
||||
settings->value("ZJUConnect/ServerAddress").toString(),
|
||||
settings->value("ZJUConnect/ServerPort").toInt(),
|
||||
@@ -210,24 +230,23 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
});
|
||||
|
||||
// 帮助-检查更新
|
||||
connect(ui->checkUpdateAction, &QAction::triggered, this,
|
||||
[&]()
|
||||
{
|
||||
checkUpdate();
|
||||
});
|
||||
connect(ui->checkUpdateAction, &QAction::triggered,
|
||||
updateChecker, &UpdateChecker::check);
|
||||
|
||||
// 帮助-项目主页
|
||||
connect(ui->projectAction, &QAction::triggered,
|
||||
[&]()
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl("https://github.com/" + Utils::REPO_NAME));
|
||||
QDesktopServices::openUrl(QUrl(
|
||||
"https://github.com/" + ApplicationConstants::RepositoryName
|
||||
));
|
||||
});
|
||||
|
||||
// 帮助-关于本软件
|
||||
connect(ui->aboutAction, &QAction::triggered,
|
||||
[&]()
|
||||
{
|
||||
Utils::showAboutMessageBox(this);
|
||||
PresentationHelpers::showAboutDialog(this);
|
||||
});
|
||||
|
||||
// 复制日志
|
||||
@@ -247,136 +266,51 @@ MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) :
|
||||
}
|
||||
);
|
||||
|
||||
connect(this, &MainWindow::SetModeFinished, this,
|
||||
[&]()
|
||||
{
|
||||
if (isFirstTimeSetMode)
|
||||
{
|
||||
isFirstTimeSetMode = false;
|
||||
bool shouldConnect = settings->value("Common/ConnectAfterStart", false).toBool();
|
||||
for (const QString &arg : qApp->arguments())
|
||||
{
|
||||
if (arg == "--connect")
|
||||
{
|
||||
shouldConnect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shouldConnect)
|
||||
{
|
||||
ui->pushButton1->click();
|
||||
}
|
||||
}
|
||||
}
|
||||
clearLog();
|
||||
connectionUiController = new ConnectionUiController(
|
||||
this,
|
||||
ui->pushButton1,
|
||||
ui->pushButton2,
|
||||
trayConnectAction,
|
||||
connectionSession,
|
||||
systemProxySession,
|
||||
authenticationDialogs,
|
||||
applicationLogger,
|
||||
coreLogFile,
|
||||
[this]() { return settings; },
|
||||
[this]() { return currentProfileId; },
|
||||
[this](
|
||||
const QString &title,
|
||||
const QString &content,
|
||||
QSystemTrayIcon::MessageIcon icon
|
||||
)
|
||||
{
|
||||
showNotification(title, content, icon);
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
// 自动检查更新
|
||||
checkUpdateNAM = new QNetworkAccessManager(this);
|
||||
|
||||
connect(checkUpdateNAM, &QNetworkAccessManager::finished,
|
||||
this, [&](QNetworkReply* reply) {
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qWarning().noquote() << "检查 UI 更新失败。原因是:" + reply->errorString();
|
||||
ui->versionLabel->setText(
|
||||
"当前版本:" + QApplication::applicationVersion() + "\n检查 UI 更新失败\n"
|
||||
);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
|
||||
reply->deleteLater();
|
||||
|
||||
QString nowVersion = QApplication::applicationVersion();
|
||||
QString latestVersion = json["tag_name"].toString();
|
||||
|
||||
// 移除开头的 'v'
|
||||
if (latestVersion.startsWith('v'))
|
||||
{
|
||||
latestVersion = latestVersion.mid(1);
|
||||
}
|
||||
qInfo().noquote() << "检查 UI 更新成功。最新版本:" + latestVersion;
|
||||
versionInfo.ui_latest = latestVersion;
|
||||
updateVersionInfo();
|
||||
|
||||
qsizetype nowVersionSuffix, latestVersionSuffix;
|
||||
auto nowVersionQ = QVersionNumber::fromString(nowVersion, &nowVersionSuffix);
|
||||
auto latestVersionQ = QVersionNumber::fromString(latestVersion, &latestVersionSuffix);
|
||||
|
||||
if (latestVersionQ > nowVersionQ ||
|
||||
(latestVersionQ == nowVersionQ && latestVersion.right(latestVersionSuffix) != nowVersion.right(nowVersionSuffix)))
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("UI 版本更新");
|
||||
msgBox.setInformativeText("存在 UI 版本更新:" + latestVersion + "\n"
|
||||
"是否前往 Github 发布页面查看?");
|
||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Ok);
|
||||
|
||||
int ret = msgBox.exec();
|
||||
if (ret == QMessageBox::Ok)
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl("https://github.com/" + Utils::REPO_NAME + "/releases/latest"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 检查核心更新
|
||||
checkCoreUpdateNAM = new QNetworkAccessManager(this);
|
||||
|
||||
connect(checkCoreUpdateNAM, &QNetworkAccessManager::finished,
|
||||
this, [&](QNetworkReply* reply) {
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qWarning().noquote() << "检查核心更新失败。原因是:" + reply->errorString();
|
||||
ui->versionLabel->setText(
|
||||
"当前版本:" + QApplication::applicationVersion() + "\n检查核心更新失败\n"
|
||||
);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json = QJsonDocument::fromJson(reply->readAll()).object();
|
||||
reply->deleteLater();
|
||||
|
||||
QString nowVersion = versionInfo.core_version;
|
||||
QString latestVersion = json["tag_name"].toString();
|
||||
|
||||
// 移除开头的 'v'
|
||||
if (latestVersion.startsWith('v'))
|
||||
{
|
||||
latestVersion = latestVersion.mid(1);
|
||||
}
|
||||
qInfo().noquote() << "检查核心更新成功。最新版本:" + latestVersion;
|
||||
versionInfo.core_latest = latestVersion;
|
||||
updateVersionInfo();
|
||||
|
||||
qsizetype nowVersionSuffix, latestVersionSuffix;
|
||||
auto nowVersionQ = QVersionNumber::fromString(nowVersion, &nowVersionSuffix);
|
||||
auto latestVersionQ = QVersionNumber::fromString(latestVersion, &latestVersionSuffix);
|
||||
|
||||
if (latestVersionQ > nowVersionQ ||
|
||||
(latestVersionQ == nowVersionQ && latestVersion.right(latestVersionSuffix) != nowVersion.right(nowVersionSuffix)))
|
||||
{
|
||||
qInfo().noquote() << "核心版本存在更新,可手动更新或通知开发者更新。";
|
||||
}
|
||||
});
|
||||
|
||||
initZjuConnect();
|
||||
bool shouldConnect =
|
||||
settings->value("Common/ConnectAfterStart", false).toBool();
|
||||
if (qApp->arguments().contains("--connect"))
|
||||
{
|
||||
shouldConnect = true;
|
||||
}
|
||||
if (shouldConnect)
|
||||
{
|
||||
ui->pushButton1->click();
|
||||
}
|
||||
|
||||
if (settings->value("Common/CheckUpdateAfterStart", true).toBool())
|
||||
{
|
||||
checkUpdate();
|
||||
updateChecker->check();
|
||||
}
|
||||
else
|
||||
{
|
||||
versionInfo.ui_latest = "已禁用";
|
||||
versionInfo.core_latest = "已禁用";
|
||||
updateVersionInfo();
|
||||
updateChecker->markDisabled();
|
||||
}
|
||||
|
||||
if (!profileManager->silentStartEnabled())
|
||||
if (!profileService->silentStartEnabled())
|
||||
{
|
||||
show();
|
||||
}
|
||||
@@ -482,12 +416,6 @@ void MainWindow::setupTrayIcon()
|
||||
void MainWindow::setupProfileMenu()
|
||||
{
|
||||
// 务必在 setupTrayIcon 之后调用,以确保 trayProfileMenu 已正确初始化
|
||||
if (profileManager == nullptr)
|
||||
{
|
||||
ui->profileMenu->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
newProfileAction = ui->profileMenu->addAction("新建配置");
|
||||
renameProfileAction = ui->profileMenu->addAction("重命名当前配置");
|
||||
deleteProfileAction = ui->profileMenu->addAction("删除当前配置");
|
||||
@@ -502,11 +430,6 @@ void MainWindow::setupProfileMenu()
|
||||
|
||||
void MainWindow::refreshProfileMenu()
|
||||
{
|
||||
if (profileManager == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const QList<QAction *> actions = ui->profileMenu->actions();
|
||||
bool remove = false;
|
||||
for (QAction *action : actions)
|
||||
@@ -558,11 +481,14 @@ void MainWindow::refreshProfileMenu()
|
||||
});
|
||||
}
|
||||
|
||||
for (const QString &profileId : profileManager->listProfiles())
|
||||
for (const QString &profileId : profileService->profiles())
|
||||
{
|
||||
QAction *action = ui->profileMenu->addAction(profileId);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(profileId == currentProfileId);
|
||||
action->setChecked(
|
||||
!profileService->usesOverrideConfiguration()
|
||||
&& profileId == currentProfileId
|
||||
);
|
||||
switchGroup->addAction(action);
|
||||
connect(action, &QAction::triggered, this, [this, profileId]()
|
||||
{
|
||||
@@ -573,7 +499,10 @@ void MainWindow::refreshProfileMenu()
|
||||
{
|
||||
QAction *trayAction = trayProfileMenu->addAction(profileId);
|
||||
trayAction->setCheckable(true);
|
||||
trayAction->setChecked(profileId == currentProfileId);
|
||||
trayAction->setChecked(
|
||||
!profileService->usesOverrideConfiguration()
|
||||
&& profileId == currentProfileId
|
||||
);
|
||||
traySwitchGroup->addAction(trayAction);
|
||||
connect(trayAction, &QAction::triggered, this, [this, profileId]()
|
||||
{
|
||||
@@ -582,24 +511,17 @@ void MainWindow::refreshProfileMenu()
|
||||
}
|
||||
}
|
||||
|
||||
renameProfileAction->setEnabled(!currentProfileId.isEmpty());
|
||||
deleteProfileAction->setEnabled(!currentProfileId.isEmpty());
|
||||
const bool currentProfileIsManaged =
|
||||
!currentProfileId.isEmpty()
|
||||
&& !profileService->usesOverrideConfiguration();
|
||||
renameProfileAction->setEnabled(currentProfileIsManaged);
|
||||
deleteProfileAction->setEnabled(currentProfileIsManaged);
|
||||
}
|
||||
|
||||
bool MainWindow::switchProfile(const QString &profileId)
|
||||
{
|
||||
if (profileManager == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(profileManager->profilePath(profileId)))
|
||||
{
|
||||
refreshProfileMenu();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profileId == currentProfileId)
|
||||
if (!profileService->usesOverrideConfiguration()
|
||||
&& profileId == currentProfileId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -616,11 +538,14 @@ bool MainWindow::switchProfile(const QString &profileId)
|
||||
settingWindow->close();
|
||||
}
|
||||
|
||||
settings->sync();
|
||||
delete settings;
|
||||
settings = new QSettings(profileManager->profilePath(profileId), QSettings::IniFormat);
|
||||
currentProfileId = profileId;
|
||||
profileManager->setActiveProfile(currentProfileId);
|
||||
if (!profileService->switchTo(profileId))
|
||||
{
|
||||
refreshProfileMenu();
|
||||
return false;
|
||||
}
|
||||
settings = profileService->settings();
|
||||
authenticationDialogs->setSettings(settings);
|
||||
currentProfileId = profileService->currentProfileId();
|
||||
|
||||
upgradeSettings();
|
||||
updateVersionInfo();
|
||||
@@ -634,8 +559,9 @@ bool MainWindow::switchProfile(const QString &profileId)
|
||||
|
||||
void MainWindow::createProfile()
|
||||
{
|
||||
if (profileManager == nullptr)
|
||||
if (connectionSession != nullptr && connectionSession->isActive())
|
||||
{
|
||||
QMessageBox::warning(this, "新建失败", "请先断开 VPN 连接,再新建配置。");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -646,23 +572,31 @@ void MainWindow::createProfile()
|
||||
return;
|
||||
}
|
||||
|
||||
const QString newProfileId = profileManager->createProfile(name, settings->fileName());
|
||||
if (connectionSession != nullptr && connectionSession->isActive())
|
||||
{
|
||||
QMessageBox::warning(this, "新建失败", "请先断开 VPN 连接,再新建配置。");
|
||||
return;
|
||||
}
|
||||
|
||||
const QString newProfileId = profileService->createAndSwitch(name);
|
||||
if (newProfileId.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, "创建失败", "无法创建新配置。");
|
||||
return;
|
||||
}
|
||||
|
||||
switchProfile(newProfileId);
|
||||
settings = profileService->settings();
|
||||
currentProfileId = profileService->currentProfileId();
|
||||
authenticationDialogs->setSettings(settings);
|
||||
upgradeSettings();
|
||||
updateVersionInfo();
|
||||
resetZjuConnectUi();
|
||||
clearLog();
|
||||
refreshProfileMenu();
|
||||
}
|
||||
|
||||
void MainWindow::renameCurrentProfile()
|
||||
{
|
||||
if (profileManager == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
QString name = QInputDialog::getText(this, "重命名配置", "请输入新配置名称:\n(仅支持字母、数字、下划线)", QLineEdit::Normal, currentProfileId, &ok);
|
||||
if (!ok)
|
||||
@@ -670,7 +604,7 @@ void MainWindow::renameCurrentProfile()
|
||||
return;
|
||||
}
|
||||
|
||||
const QString normalizedName = profileManager->normalizeProfileId(name);
|
||||
const QString normalizedName = profileService->normalizeProfileId(name);
|
||||
if (normalizedName.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, "重命名失败", "配置名称不能为空。");
|
||||
@@ -686,21 +620,19 @@ void MainWindow::renameCurrentProfile()
|
||||
return;
|
||||
}
|
||||
|
||||
settings->sync();
|
||||
if (settingWindow != nullptr)
|
||||
{
|
||||
settingWindow->close();
|
||||
}
|
||||
if (!profileManager->renameProfile(currentProfileId, normalizedName))
|
||||
if (!profileService->renameCurrent(normalizedName))
|
||||
{
|
||||
QMessageBox::warning(this, "重命名失败", "目标配置已存在,或当前配置不可重命名。");
|
||||
return;
|
||||
}
|
||||
|
||||
currentProfileId = normalizedName;
|
||||
profileManager->setActiveProfile(currentProfileId);
|
||||
delete settings;
|
||||
settings = new QSettings(profileManager->profilePath(currentProfileId), QSettings::IniFormat);
|
||||
currentProfileId = profileService->currentProfileId();
|
||||
settings = profileService->settings();
|
||||
authenticationDialogs->setSettings(settings);
|
||||
updateVersionInfo();
|
||||
refreshProfileMenu();
|
||||
clearLog();
|
||||
@@ -709,11 +641,6 @@ void MainWindow::renameCurrentProfile()
|
||||
|
||||
void MainWindow::deleteCurrentProfile()
|
||||
{
|
||||
if (profileManager == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentProfileId.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, "删除失败", "默认配置不可删除。");
|
||||
@@ -732,56 +659,42 @@ void MainWindow::deleteCurrentProfile()
|
||||
}
|
||||
|
||||
const QString removedProfileId = currentProfileId;
|
||||
if (!switchProfile(""))
|
||||
if (connectionSession != nullptr && connectionSession->isActive())
|
||||
{
|
||||
QMessageBox::warning(this, "删除失败", "请先断开 VPN 连接,再删除配置。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!profileManager->removeProfile(removedProfileId))
|
||||
if (settingWindow != nullptr)
|
||||
{
|
||||
settingWindow->close();
|
||||
}
|
||||
if (!profileService->removeCurrentAndSwitchToDefault())
|
||||
{
|
||||
QMessageBox::warning(this, "删除失败", "无法删除该配置文件。");
|
||||
return;
|
||||
}
|
||||
|
||||
currentProfileId = profileService->currentProfileId();
|
||||
settings = profileService->settings();
|
||||
authenticationDialogs->setSettings(settings);
|
||||
upgradeSettings();
|
||||
updateVersionInfo();
|
||||
resetZjuConnectUi();
|
||||
clearLog();
|
||||
refreshProfileMenu();
|
||||
qInfo().noquote() << "已删除配置:" + removedProfileId;
|
||||
}
|
||||
|
||||
void MainWindow::checkUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
versionInfo.core_version = Utils::checkCoreVersion(this);
|
||||
qInfo().noquote() << "检查核心版本成功:" + versionInfo.core_version;
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
qWarning().noquote() << "检查核心版本失败:" + QString(e.what());
|
||||
versionInfo.core_version = "错误";
|
||||
}
|
||||
QNetworkRequest request(QUrl("https://api.github.com/repos/" + Utils::REPO_NAME + "/releases/latest"));
|
||||
checkUpdateNAM->get(request);
|
||||
QNetworkRequest request_c(QUrl("https://api.github.com/repos/" + Utils::CORE_REPO_NAME + "/releases/latest"));
|
||||
checkCoreUpdateNAM->get(request_c);
|
||||
}
|
||||
|
||||
void MainWindow::upgradeSettings()
|
||||
{
|
||||
int configVersion = settings->value("Common/ConfigVersion", -1).toInt();
|
||||
|
||||
if (configVersion == -1)
|
||||
const SettingsMigrationAction action = SettingsMigrator::prepare(*settings);
|
||||
if (action == SettingsMigrationAction::MigrateAutoStart)
|
||||
{
|
||||
Utils::resetDefaultSettings(*settings);
|
||||
profileService->migrateAutoStartSetting(
|
||||
settings->value("Common/AutoStart", false).toBool()
|
||||
);
|
||||
}
|
||||
else if (configVersion == 4)
|
||||
{
|
||||
settings->setValue("ZJUConnect/Protocol", "easyconnect");
|
||||
}
|
||||
else if (configVersion == 6)
|
||||
{
|
||||
profileManager->setAutoStartEnabled(settings->value("Common/AutoStart", false).toBool());
|
||||
}
|
||||
else if (configVersion < Utils::CONFIG_VERSION)
|
||||
else if (action == SettingsMigrationAction::RecommendReset)
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("存在配置更新");
|
||||
@@ -789,23 +702,23 @@ void MainWindow::upgradeSettings()
|
||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Ok)
|
||||
const bool reset = msgBox.exec() == QMessageBox::Ok;
|
||||
SettingsMigrator::finish(*settings, reset);
|
||||
if (reset)
|
||||
{
|
||||
settings->clear();
|
||||
Utils::resetDefaultSettings(*settings);
|
||||
QMessageBox::information(this, "完成", "已恢复默认设置。");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
settings->setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
|
||||
settings->sync();
|
||||
SettingsMigrator::finish(*settings, false);
|
||||
}
|
||||
|
||||
void MainWindow::updateVersionInfo()
|
||||
{
|
||||
const VersionInfo &versionInfo = updateChecker->versionInfo();
|
||||
ui->versionLabel->setText(
|
||||
"UI 版本:" + versionInfo.ui_version + " 最新:" + versionInfo.ui_latest + "\n"
|
||||
"核心版本:" + versionInfo.core_version + " 最新:" + versionInfo.core_latest + "\n"
|
||||
"UI 版本:" + versionInfo.uiVersion + " 最新:" + versionInfo.uiLatest + "\n"
|
||||
"核心版本:" + versionInfo.coreVersion + " 最新:" + versionInfo.coreLatest + "\n"
|
||||
"当前配置:" + (currentProfileId.isEmpty() ? "默认" : currentProfileId)
|
||||
);
|
||||
}
|
||||
@@ -832,17 +745,18 @@ void MainWindow::showNotification(const QString &title, const QString &content,
|
||||
void MainWindow::cleanUpWhenQuit()
|
||||
{
|
||||
// 保存配置
|
||||
if (settings->value("Common/ConfigVersion", 0).toInt() <= Utils::CONFIG_VERSION)
|
||||
if (settings->value("Common/ConfigVersion", 0).toInt() <=
|
||||
ApplicationConstants::ConfigVersion)
|
||||
{
|
||||
settings->setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
|
||||
settings->setValue(
|
||||
"Common/ConfigVersion",
|
||||
ApplicationConstants::ConfigVersion
|
||||
);
|
||||
}
|
||||
settings->sync();
|
||||
|
||||
// 清除系统代理
|
||||
if (systemProxySession != nullptr)
|
||||
{
|
||||
systemProxySession->clearBeforeShutdown();
|
||||
}
|
||||
coordinator->prepareForShutdown();
|
||||
}
|
||||
|
||||
void MainWindow::gracefullyQuit()
|
||||
@@ -861,23 +775,5 @@ void MainWindow::gracefullyQuit()
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (settings != nullptr)
|
||||
{
|
||||
settings->sync();
|
||||
delete settings;
|
||||
}
|
||||
|
||||
if (profileManager != nullptr)
|
||||
{
|
||||
delete profileManager;
|
||||
}
|
||||
|
||||
if (connectionSession != nullptr)
|
||||
{
|
||||
disconnect(connectionSession, nullptr, this, nullptr);
|
||||
delete connectionSession;
|
||||
connectionSession = nullptr;
|
||||
}
|
||||
|
||||
delete ui;
|
||||
}
|
||||
@@ -4,20 +4,15 @@
|
||||
#include <QMainWindow>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QCloseEvent>
|
||||
#include <QProcess>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
#include <QPointer>
|
||||
|
||||
#include "application/connectionsession.h"
|
||||
#include "application/profileservice.h"
|
||||
#include "application/systemproxysession.h"
|
||||
#include "loginwindow/loginwindow.h"
|
||||
#include "sudowindow/sudowindow.h"
|
||||
#include "ssologinwebview/ssologinwebview.h"
|
||||
#include "zjuconnectcontroller/zjuconnectcontroller.h"
|
||||
#include "settingwindow/settingwindow.h"
|
||||
#include "graphcaptchawindow/graphcaptchawindow.h"
|
||||
#include "utils/profilemanager.h"
|
||||
#include "presentation/dialogs/settingwindow/settingwindow.h"
|
||||
#include "presentation/coordinators/authdialogcoordinator.h"
|
||||
#include "presentation/main/mainwindowcoordinator.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@@ -25,7 +20,9 @@ namespace Ui
|
||||
}
|
||||
|
||||
class ApplicationLogger;
|
||||
class ConnectionUiController;
|
||||
class CoreLogFile;
|
||||
class UpdateChecker;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
@@ -40,18 +37,12 @@ public slots:
|
||||
|
||||
void cleanUpWhenQuit();
|
||||
|
||||
signals:
|
||||
|
||||
void SetModeFinished();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
void changeEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void checkUpdate();
|
||||
|
||||
void upgradeSettings();
|
||||
|
||||
void clearLog();
|
||||
@@ -64,8 +55,6 @@ private:
|
||||
QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon::Information
|
||||
);
|
||||
|
||||
void initZjuConnect();
|
||||
|
||||
void updateVersionInfo();
|
||||
|
||||
void setupTrayIcon();
|
||||
@@ -84,11 +73,6 @@ private:
|
||||
|
||||
void gracefullyQuit();
|
||||
|
||||
struct {
|
||||
QString ui_version, ui_latest;
|
||||
QString core_version, core_latest;
|
||||
} versionInfo;
|
||||
|
||||
Ui::MainWindow *ui;
|
||||
QSystemTrayIcon *trayIcon;
|
||||
QMenu *trayMenu;
|
||||
@@ -101,23 +85,17 @@ private:
|
||||
QAction *deleteProfileAction;
|
||||
ConnectionSession *connectionSession = nullptr;
|
||||
SystemProxySession *systemProxySession = nullptr;
|
||||
AuthDialogCoordinator *authenticationDialogs = nullptr;
|
||||
ConnectionUiController *connectionUiController;
|
||||
MainWindowCoordinator *coordinator;
|
||||
ApplicationLogger *applicationLogger;
|
||||
CoreLogFile *coreLogFile = nullptr;
|
||||
QNetworkAccessManager *checkUpdateNAM;
|
||||
QNetworkAccessManager *checkCoreUpdateNAM;
|
||||
UpdateChecker *updateChecker;
|
||||
QSettings *settings;
|
||||
ProfileManager *profileManager;
|
||||
ProfileService *profileService;
|
||||
QString currentProfileId;
|
||||
|
||||
QObject *diagnosisContext;
|
||||
|
||||
QPointer<SettingWindow> settingWindow;
|
||||
QPointer<LoginWindow> loginWindow;
|
||||
QPointer<SudoWindow> sudoWindow;
|
||||
QPointer<SsoLoginWebView> ssoLoginWebView;
|
||||
QPointer<GraphCaptchaWindow> graphCaptchaWindow;
|
||||
|
||||
bool isFirstTimeSetMode;
|
||||
|
||||
};
|
||||
|
||||
135
presentation/main/mainwindowcoordinator.cpp
Normal file
135
presentation/main/mainwindowcoordinator.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "mainwindowcoordinator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "application/connectionsession.h"
|
||||
#include "application/profileservice.h"
|
||||
#include "application/systemproxysession.h"
|
||||
#include "infrastructure/coreprocess/zjuconnectprocess.h"
|
||||
#include "infrastructure/platform/platformsystemproxybackend.h"
|
||||
#include "infrastructure/settings/profilemanager.h"
|
||||
#include "infrastructure/update/updatechecker.h"
|
||||
#include "presentation/coordinators/authdialogcoordinator.h"
|
||||
|
||||
MainWindowCoordinator::MainWindowCoordinator(
|
||||
QWidget *parentWidget,
|
||||
const QString &overrideConfigPath,
|
||||
QObject *parent
|
||||
)
|
||||
: QObject(parent),
|
||||
profileService(new ProfileService(
|
||||
std::make_unique<ProfileManager>(),
|
||||
overrideConfigPath,
|
||||
this
|
||||
)),
|
||||
connectionSession(new ConnectionSession(new ZjuConnectProcess(), this)),
|
||||
systemProxySession(new SystemProxySession(
|
||||
std::make_unique<PlatformSystemProxyBackend>(),
|
||||
this
|
||||
)),
|
||||
updateChecker(new UpdateChecker(this)),
|
||||
authenticationCoordinator(new AuthDialogCoordinator(
|
||||
parentWidget,
|
||||
profileService->settings(),
|
||||
this
|
||||
))
|
||||
{
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::askSudoPass,
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::requestSudoPassword
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::graphCaptcha,
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::requestGraphCaptcha
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::smsCode,
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::requestSmsCode
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::totpCode,
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::requestTotpCode
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::ssoAuth,
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::requestSsoLogin
|
||||
);
|
||||
connect(
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::sudoPasswordSubmitted,
|
||||
connectionSession,
|
||||
&ConnectionSession::submitSudoPassword
|
||||
);
|
||||
connect(
|
||||
authenticationCoordinator,
|
||||
&AuthDialogCoordinator::interactiveInputSubmitted,
|
||||
connectionSession,
|
||||
&ConnectionSession::submitInput
|
||||
);
|
||||
|
||||
connect(
|
||||
systemProxySession,
|
||||
&SystemProxySession::operationFinished,
|
||||
this,
|
||||
[this](bool enabled)
|
||||
{
|
||||
if (enabled && !connectionSession->isActive())
|
||||
{
|
||||
systemProxySession->disable();
|
||||
}
|
||||
}
|
||||
);
|
||||
connect(
|
||||
connectionSession,
|
||||
&ConnectionSession::finished,
|
||||
this,
|
||||
[this](ZJU_ERROR)
|
||||
{
|
||||
if (systemProxySession->isEnabled())
|
||||
{
|
||||
systemProxySession->disable();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ProfileService *MainWindowCoordinator::profiles() const
|
||||
{
|
||||
return profileService;
|
||||
}
|
||||
|
||||
ConnectionSession *MainWindowCoordinator::connection() const
|
||||
{
|
||||
return connectionSession;
|
||||
}
|
||||
|
||||
SystemProxySession *MainWindowCoordinator::systemProxy() const
|
||||
{
|
||||
return systemProxySession;
|
||||
}
|
||||
|
||||
UpdateChecker *MainWindowCoordinator::updates() const
|
||||
{
|
||||
return updateChecker;
|
||||
}
|
||||
|
||||
AuthDialogCoordinator *
|
||||
MainWindowCoordinator::authenticationDialogs() const
|
||||
{
|
||||
return authenticationCoordinator;
|
||||
}
|
||||
|
||||
void MainWindowCoordinator::prepareForShutdown()
|
||||
{
|
||||
systemProxySession->clearBeforeShutdown();
|
||||
}
|
||||
40
presentation/main/mainwindowcoordinator.h
Normal file
40
presentation/main/mainwindowcoordinator.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef MAINWINDOWCOORDINATOR_H
|
||||
#define MAINWINDOWCOORDINATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class AuthDialogCoordinator;
|
||||
class ConnectionSession;
|
||||
class ProfileService;
|
||||
class SystemProxySession;
|
||||
class UpdateChecker;
|
||||
class QWidget;
|
||||
|
||||
class MainWindowCoordinator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindowCoordinator(
|
||||
QWidget *parentWidget,
|
||||
const QString &overrideConfigPath,
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
ProfileService *profiles() const;
|
||||
ConnectionSession *connection() const;
|
||||
SystemProxySession *systemProxy() const;
|
||||
UpdateChecker *updates() const;
|
||||
AuthDialogCoordinator *authenticationDialogs() const;
|
||||
|
||||
void prepareForShutdown();
|
||||
|
||||
private:
|
||||
ProfileService *profileService;
|
||||
ConnectionSession *connectionSession;
|
||||
SystemProxySession *systemProxySession;
|
||||
UpdateChecker *updateChecker;
|
||||
AuthDialogCoordinator *authenticationCoordinator;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOWCOORDINATOR_H
|
||||
94
presentation/presentationhelpers.cpp
Normal file
94
presentation/presentationhelpers.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "presentationhelpers.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QPixmap>
|
||||
#include <QSizePolicy>
|
||||
#include <QWidget>
|
||||
|
||||
#include "application/applicationconstants.h"
|
||||
|
||||
void PresentationHelpers::retainSizeWhenHidden(QWidget *widget)
|
||||
{
|
||||
QSizePolicy policy = widget->sizePolicy();
|
||||
policy.setRetainSizeWhenHidden(true);
|
||||
widget->setSizePolicy(policy);
|
||||
}
|
||||
|
||||
void PresentationHelpers::showAboutDialog(QWidget *parent)
|
||||
{
|
||||
QMessageBox messageBox(parent);
|
||||
messageBox.setWindowTitle("关于");
|
||||
messageBox.setTextFormat(Qt::RichText);
|
||||
const QString repository = ApplicationConstants::RepositoryName;
|
||||
messageBox.setText(
|
||||
QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() +
|
||||
"<br>改进的 ZJU-Connect 图形界面" +
|
||||
"<br>作者:<a href='https://github.com/chenx-dust'>Chenx Dust</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/" + repository +
|
||||
"'>https://github.com/" + repository + "</a>" +
|
||||
"<br><br><b>致谢:</b>" +
|
||||
"<br><br>ZJU-Connect-for-Windows" +
|
||||
"<br>基于 Qt 编写的 ZJU 网络客户端" +
|
||||
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/Mythologyli/ZJU-Connect-for-Windows'>"
|
||||
"https://github.com/Mythologyli/ZJU-Connect-for-Windows</a>" +
|
||||
"<br><br>zju-connect" +
|
||||
"<br>ZJU RVPN 客户端的 Go 语言实现" +
|
||||
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/Mythologyli/zju-connect'>"
|
||||
"https://github.com/Mythologyli/zju-connect</a>" +
|
||||
"<br><br>EasierConnect" +
|
||||
"<br>EasyConnect 客户端的开源实现" +
|
||||
"<br>作者:<a href='https://github.com/lyc8503'>lyc8503</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/lyc8503/EasierConnect'>"
|
||||
"https://github.com/lyc8503/EasierConnect</a>"
|
||||
);
|
||||
messageBox.setIconPixmap(QPixmap(":/resource/icon.png").scaled(
|
||||
100,
|
||||
100,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
));
|
||||
messageBox.exec();
|
||||
}
|
||||
|
||||
bool PresentationHelpers::confirmCredentials(
|
||||
const QString &username,
|
||||
const QString &password
|
||||
)
|
||||
{
|
||||
if (username.isEmpty() || password.isEmpty())
|
||||
{
|
||||
return QMessageBox::warning(
|
||||
nullptr,
|
||||
"警告",
|
||||
"账号或密码为空!\n\n是否继续?",
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Cancel
|
||||
) == QMessageBox::Ok;
|
||||
}
|
||||
|
||||
const auto containsNonAscii = [](const QString &value)
|
||||
{
|
||||
for (const QChar character : value)
|
||||
{
|
||||
if (character.unicode() > 127)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (containsNonAscii(username) || containsNonAscii(password))
|
||||
{
|
||||
return QMessageBox::warning(
|
||||
nullptr,
|
||||
"警告",
|
||||
"账号或密码存在非 ASCII 字符!\n建议检查输入法设置。\n\n是否继续?",
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Cancel
|
||||
) == QMessageBox::Ok;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
15
presentation/presentationhelpers.h
Normal file
15
presentation/presentationhelpers.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef PRESENTATIONHELPERS_H
|
||||
#define PRESENTATIONHELPERS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
|
||||
namespace PresentationHelpers
|
||||
{
|
||||
void retainSizeWhenHidden(QWidget *widget);
|
||||
void showAboutDialog(QWidget *parent = nullptr);
|
||||
bool confirmCredentials(const QString &username, const QString &password);
|
||||
}
|
||||
|
||||
#endif // PRESENTATIONHELPERS_H
|
||||
114
tests/connectionsession_test.cpp
Normal file
114
tests/connectionsession_test.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "application/connectionsession.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class FakeCoreProcess : public CoreProcess
|
||||
{
|
||||
public:
|
||||
int startCalls = 0;
|
||||
int stopCalls = 0;
|
||||
QByteArray lastInput;
|
||||
|
||||
void start(const ConnectionProfile &) override
|
||||
{
|
||||
++startCalls;
|
||||
emit started();
|
||||
}
|
||||
|
||||
void stop() override
|
||||
{
|
||||
++stopCalls;
|
||||
}
|
||||
|
||||
void writeInput(const QByteArray &data) override
|
||||
{
|
||||
lastInput = data;
|
||||
}
|
||||
|
||||
void complete()
|
||||
{
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void requestSudoPassword()
|
||||
{
|
||||
emit askSudoPass();
|
||||
}
|
||||
};
|
||||
|
||||
bool delegatesProcessLifecycleThroughPort()
|
||||
{
|
||||
auto *coreProcess = new FakeCoreProcess();
|
||||
ConnectionSession session(coreProcess);
|
||||
ConnectionProfile profile;
|
||||
int finishedCalls = 0;
|
||||
QObject::connect(
|
||||
&session,
|
||||
&ConnectionSession::finished,
|
||||
[&](ZJU_ERROR) { ++finishedCalls; }
|
||||
);
|
||||
|
||||
if (!session.start(profile, {})
|
||||
|| coreProcess->startCalls != 1
|
||||
|| session.state() != ConnectionState::Running
|
||||
|| !session.isActive()
|
||||
|| session.start(profile, {}))
|
||||
{
|
||||
qCritical() << "start delegation failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
session.submitInput("input");
|
||||
session.stop();
|
||||
if (coreProcess->lastInput != "input"
|
||||
|| coreProcess->stopCalls != 1
|
||||
|| session.state() != ConnectionState::Stopping)
|
||||
{
|
||||
qCritical() << "input or stop delegation failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
coreProcess->complete();
|
||||
if (finishedCalls != 1
|
||||
|| session.state() != ConnectionState::Disconnected
|
||||
|| session.isActive())
|
||||
{
|
||||
qCritical() << "completion delegation failed";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool emptySudoPasswordStopsTheSession()
|
||||
{
|
||||
auto *coreProcess = new FakeCoreProcess();
|
||||
ConnectionSession session(coreProcess);
|
||||
ConnectionProfile profile;
|
||||
|
||||
if (!session.start(profile, {}))
|
||||
{
|
||||
qCritical() << "failed to start session for sudo cancellation";
|
||||
return false;
|
||||
}
|
||||
|
||||
coreProcess->requestSudoPassword();
|
||||
session.submitSudoPassword({}, false);
|
||||
if (coreProcess->stopCalls != 1
|
||||
|| session.state() != ConnectionState::Stopping)
|
||||
{
|
||||
qCritical() << "empty sudo password did not stop the session";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication application(argc, argv);
|
||||
return delegatesProcessLifecycleThroughPort()
|
||||
&& emptySudoPasswordStopsTheSession() ? 0 : 1;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "core/corecommandbuilder.h"
|
||||
#include "infrastructure/coreprocess/corecommandbuilder.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "infrastructure/corelogfile.h"
|
||||
#include "infrastructure/logging/corelogfile.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "core/coreoutputbuffer.h"
|
||||
#include "infrastructure/coreprocess/coreoutputbuffer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
|
||||
#include "core/coreoutputparser.h"
|
||||
#include "infrastructure/coreprocess/coreoutputparser.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
40
tests/profilemanager_test.cpp
Normal file
40
tests/profilemanager_test.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "infrastructure/settings/profilemanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool createsPhysicalFilesForEmptyProfiles()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
ProfileManager manager(directory.path());
|
||||
|
||||
if (!manager.activeProfile().isEmpty()
|
||||
|| !QFileInfo::exists(manager.profilePath(""))
|
||||
|| !manager.setActiveProfile(""))
|
||||
{
|
||||
qCritical() << "default profile file was not created";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString profileId = manager.createProfile("empty");
|
||||
if (profileId != "empty"
|
||||
|| !QFileInfo::exists(manager.profilePath(profileId))
|
||||
|| !manager.listProfiles().contains(profileId)
|
||||
|| !manager.setActiveProfile(profileId))
|
||||
{
|
||||
qCritical() << "empty named profile file was not created";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication application(argc, argv);
|
||||
return createsPhysicalFilesForEmptyProfiles() ? 0 : 1;
|
||||
}
|
||||
270
tests/profileservice_test.cpp
Normal file
270
tests/profileservice_test.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "application/profilebackend.h"
|
||||
#include "application/profileservice.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class FakeProfileBackend : public ProfileBackend
|
||||
{
|
||||
public:
|
||||
explicit FakeProfileBackend(const QString &rootPath)
|
||||
: rootPath(rootPath)
|
||||
{
|
||||
QSettings defaultSettings(profilePath(""), QSettings::IniFormat);
|
||||
defaultSettings.sync();
|
||||
}
|
||||
|
||||
QStringList listProfiles() const override
|
||||
{
|
||||
QStringList result;
|
||||
for (const QFileInfo &file : QDir(rootPath).entryInfoList(
|
||||
{"*.ini"},
|
||||
QDir::Files,
|
||||
QDir::Name
|
||||
))
|
||||
{
|
||||
if (file.baseName() != "default")
|
||||
{
|
||||
result << file.baseName();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString activeProfile() const override
|
||||
{
|
||||
return activeId;
|
||||
}
|
||||
|
||||
bool setActiveProfile(const QString &profileId) const override
|
||||
{
|
||||
if (!allowActiveProfileChange)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
activeId = profileId;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString profilePath(const QString &profileId) const override
|
||||
{
|
||||
return QDir(rootPath).filePath(
|
||||
(profileId.isEmpty() ? QStringLiteral("default") : profileId) + ".ini"
|
||||
);
|
||||
}
|
||||
|
||||
QString createProfile(
|
||||
const QString &requestedName,
|
||||
const QString &sourcePath
|
||||
) const override
|
||||
{
|
||||
const QString id = normalizeProfileId(requestedName);
|
||||
return !id.isEmpty() && QFile::copy(sourcePath, profilePath(id)) ? id : QString();
|
||||
}
|
||||
|
||||
bool renameProfile(
|
||||
const QString &oldId,
|
||||
const QString &newId
|
||||
) const override
|
||||
{
|
||||
return QFile::rename(profilePath(oldId), profilePath(newId));
|
||||
}
|
||||
|
||||
bool removeProfile(const QString &profileId) const override
|
||||
{
|
||||
return allowProfileRemoval && QFile::remove(profilePath(profileId));
|
||||
}
|
||||
|
||||
QString normalizeProfileId(const QString &name) const override
|
||||
{
|
||||
return name.trimmed();
|
||||
}
|
||||
|
||||
bool silentStartEnabled() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void setAutoStartEnabled(bool enabled) const override
|
||||
{
|
||||
migratedAutoStart = enabled;
|
||||
}
|
||||
|
||||
mutable QString activeId;
|
||||
mutable bool migratedAutoStart = false;
|
||||
bool allowActiveProfileChange = true;
|
||||
bool allowProfileRemoval = true;
|
||||
|
||||
private:
|
||||
QString rootPath;
|
||||
};
|
||||
|
||||
bool managesCurrentProfileAsOneUnit()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
auto backend = std::make_unique<FakeProfileBackend>(directory.path());
|
||||
FakeProfileBackend *fake = backend.get();
|
||||
ProfileService service(std::move(backend));
|
||||
service.settings()->setValue("Credential/Username", "alice");
|
||||
service.settings()->sync();
|
||||
|
||||
fake->allowActiveProfileChange = false;
|
||||
if (!service.createAndSwitch("blocked").isEmpty()
|
||||
|| service.currentProfileId() != ""
|
||||
|| QFileInfo::exists(fake->profilePath("blocked")))
|
||||
{
|
||||
qCritical() << "failed activation left a created profile behind";
|
||||
return false;
|
||||
}
|
||||
fake->allowActiveProfileChange = true;
|
||||
|
||||
if (!service.switchTo("missing"))
|
||||
{
|
||||
// Expected: missing profiles are rejected.
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "missing profile was accepted";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service.createAndSwitch("work") != "work"
|
||||
|| service.currentProfileId() != "work"
|
||||
|| service.settings()->value("Credential/Username").toString() != "alice")
|
||||
{
|
||||
qCritical() << "profile creation failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
fake->allowActiveProfileChange = false;
|
||||
QSettings *workSettings = service.settings();
|
||||
if (service.renameCurrent("blocked")
|
||||
|| service.currentProfileId() != "work"
|
||||
|| service.settings() != workSettings
|
||||
|| !QFileInfo::exists(fake->profilePath("work"))
|
||||
|| QFileInfo::exists(fake->profilePath("blocked")))
|
||||
{
|
||||
qCritical() << "failed activation did not roll back profile rename";
|
||||
return false;
|
||||
}
|
||||
fake->allowActiveProfileChange = true;
|
||||
|
||||
if (!service.renameCurrent("renamed")
|
||||
|| service.currentProfileId() != "renamed")
|
||||
{
|
||||
qCritical() << "profile rename failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
QSettings *renamedSettings = service.settings();
|
||||
fake->allowActiveProfileChange = false;
|
||||
if (service.removeCurrentAndSwitchToDefault()
|
||||
|| service.currentProfileId() != "renamed"
|
||||
|| service.settings() != renamedSettings
|
||||
|| !QFileInfo::exists(fake->profilePath("renamed"))
|
||||
|| fake->activeId != "renamed")
|
||||
{
|
||||
qCritical() << "failed default activation changed the current profile";
|
||||
return false;
|
||||
}
|
||||
fake->allowActiveProfileChange = true;
|
||||
|
||||
fake->allowProfileRemoval = false;
|
||||
if (service.removeCurrentAndSwitchToDefault()
|
||||
|| service.currentProfileId() != "renamed"
|
||||
|| service.settings() != renamedSettings
|
||||
|| !QFileInfo::exists(fake->profilePath("renamed"))
|
||||
|| fake->activeId != "renamed")
|
||||
{
|
||||
qCritical() << "failed removal changed the current profile";
|
||||
return false;
|
||||
}
|
||||
|
||||
fake->allowProfileRemoval = true;
|
||||
service.migrateAutoStartSetting(true);
|
||||
if (!fake->migratedAutoStart
|
||||
|| !service.removeCurrentAndSwitchToDefault()
|
||||
|| !service.currentProfileId().isEmpty())
|
||||
{
|
||||
qCritical() << "profile removal or global setting migration failed";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool acceptsCustomAsARegularProfileId()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
auto backend = std::make_unique<FakeProfileBackend>(directory.path());
|
||||
FakeProfileBackend *fake = backend.get();
|
||||
ProfileService service(std::move(backend));
|
||||
service.settings()->setValue("Credential/Username", "custom-user");
|
||||
service.settings()->sync();
|
||||
|
||||
if (service.createAndSwitch("custom") != "custom"
|
||||
|| service.usesOverrideConfiguration()
|
||||
|| service.currentProfileId() != "custom")
|
||||
{
|
||||
qCritical() << "custom profile creation failed";
|
||||
return false;
|
||||
}
|
||||
if (!service.renameCurrent("renamed")
|
||||
|| service.currentProfileId() != "renamed")
|
||||
{
|
||||
qCritical() << "custom profile rename failed";
|
||||
return false;
|
||||
}
|
||||
if (!service.removeCurrentAndSwitchToDefault())
|
||||
{
|
||||
qCritical() << "renamed custom profile removal failed";
|
||||
return false;
|
||||
}
|
||||
return fake->activeId.isEmpty();
|
||||
}
|
||||
|
||||
bool switchesAwayFromAnOverrideNamedCustom()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
const QString overridePath = directory.filePath("override.ini");
|
||||
const QString profilesPath = directory.filePath("profiles");
|
||||
QDir().mkpath(profilesPath);
|
||||
{
|
||||
QSettings overrideSettings(overridePath, QSettings::IniFormat);
|
||||
overrideSettings.setValue("Credential/Username", "override-user");
|
||||
overrideSettings.sync();
|
||||
}
|
||||
|
||||
auto backend = std::make_unique<FakeProfileBackend>(profilesPath);
|
||||
FakeProfileBackend *fake = backend.get();
|
||||
ProfileService service(std::move(backend), overridePath);
|
||||
|
||||
if (!service.usesOverrideConfiguration()
|
||||
|| service.createAndSwitch("custom") != "custom"
|
||||
|| service.usesOverrideConfiguration()
|
||||
|| service.currentProfileId() != "custom"
|
||||
|| service.settings()->fileName() != fake->profilePath("custom")
|
||||
|| fake->activeId != "custom")
|
||||
{
|
||||
qCritical() << "override configuration was confused with profile custom";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication application(argc, argv);
|
||||
return managesCurrentProfileAsOneUnit()
|
||||
&& acceptsCustomAsARegularProfileId()
|
||||
&& switchesAwayFromAnOverrideNamedCustom() ? 0 : 1;
|
||||
}
|
||||
47
tests/settingsmigrator_test.cpp
Normal file
47
tests/settingsmigrator_test.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "application/settingsmigrator.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool migratesKnownVersions()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QSettings settings(directory.filePath("profile.ini"), QSettings::IniFormat);
|
||||
settings.setValue("Common/ConfigVersion", 4);
|
||||
|
||||
const auto action = SettingsMigrator::prepare(settings);
|
||||
const bool passed =
|
||||
action == SettingsMigrationAction::None
|
||||
&& settings.value("ZJUConnect/Protocol").toString() == "easyconnect";
|
||||
if (!passed)
|
||||
{
|
||||
qCritical() << "migratesKnownVersions failed";
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool recommendsResetForUnsupportedVersions()
|
||||
{
|
||||
QTemporaryDir directory;
|
||||
QSettings settings(directory.filePath("profile.ini"), QSettings::IniFormat);
|
||||
settings.setValue("Common/ConfigVersion", 5);
|
||||
const bool passed =
|
||||
SettingsMigrator::prepare(settings) ==
|
||||
SettingsMigrationAction::RecommendReset;
|
||||
if (!passed)
|
||||
{
|
||||
qCritical() << "recommendsResetForUnsupportedVersions failed";
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
return migratesKnownVersions() && recommendsResetForUnsupportedVersions() ? 0 : 1;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <QSettings>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "infrastructure/settingsprofileloader.h"
|
||||
#include "infrastructure/settings/settingsprofileloader.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
88
tests/updatechecker_test.cpp
Normal file
88
tests/updatechecker_test.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "infrastructure/update/updatechecker.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool normalizesTags()
|
||||
{
|
||||
const bool passed =
|
||||
UpdateChecker::normalizeVersionTag("v1.2.3") == "1.2.3"
|
||||
&& UpdateChecker::normalizeVersionTag("1.2.3") == "1.2.3";
|
||||
if (!passed)
|
||||
{
|
||||
qCritical() << "normalizesTags failed";
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool validatesReleaseResponses()
|
||||
{
|
||||
const std::optional<QString> version =
|
||||
UpdateChecker::parseReleaseVersion(R"({"tag_name":"v1.2.3"})");
|
||||
const bool passed =
|
||||
version == std::optional<QString>("1.2.3")
|
||||
&& !UpdateChecker::parseReleaseVersion("not json").has_value()
|
||||
&& !UpdateChecker::parseReleaseVersion("{}").has_value()
|
||||
&& !UpdateChecker::parseReleaseVersion(
|
||||
R"({"tag_name":""})"
|
||||
).has_value()
|
||||
&& !UpdateChecker::parseReleaseVersion(
|
||||
R"({"tag_name":42})"
|
||||
).has_value();
|
||||
if (!passed)
|
||||
{
|
||||
qCritical() << "validatesReleaseResponses failed";
|
||||
}
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool comparesVersions()
|
||||
{
|
||||
struct VersionComparison
|
||||
{
|
||||
QString current;
|
||||
QString latest;
|
||||
bool expected;
|
||||
};
|
||||
const QList<VersionComparison> comparisons{
|
||||
{"1.2.3", "1.2.4", true},
|
||||
{"1.2.3-beta", "1.2.3", true},
|
||||
{"1.2.3-pre.2", "1.2.3-pre.10", true},
|
||||
{"1.2.3", "1.2.3-hotfix", true},
|
||||
{"1.2.3-hotfix.2", "1.2.3-hotfix.10", true},
|
||||
{"1.2.3", "1.2.3", false},
|
||||
{"1.2.3", "1.2.3-beta", false},
|
||||
{"1.2.3-hotfix", "1.2.3", false},
|
||||
{"1.3.0", "1.2.9", false}
|
||||
};
|
||||
|
||||
for (const VersionComparison &comparison : comparisons)
|
||||
{
|
||||
const bool actual = UpdateChecker::isNewerVersion(
|
||||
comparison.current,
|
||||
comparison.latest
|
||||
);
|
||||
if (actual != comparison.expected)
|
||||
{
|
||||
qCritical()
|
||||
<< "comparesVersions failed:"
|
||||
<< comparison.current
|
||||
<< comparison.latest
|
||||
<< "expected" << comparison.expected
|
||||
<< "actual" << actual;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
return normalizesTags()
|
||||
&& validatesReleaseResponses()
|
||||
&& comparesVersions() ? 0 : 1;
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
#include <QProcess>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace Utils {
|
||||
QString getCorePath()
|
||||
{
|
||||
QString program_filename;
|
||||
if (QSysInfo::productType() == "windows")
|
||||
{
|
||||
program_filename = "zju-connect.exe";
|
||||
}
|
||||
else
|
||||
{
|
||||
program_filename = "zju-connect";
|
||||
}
|
||||
QString path = QCoreApplication::applicationDirPath() + "/" + program_filename;
|
||||
if (!QFileInfo::exists(path)) {
|
||||
return program_filename; // 可能在 PATH 中
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
QString checkCoreVersion(QObject* parent)
|
||||
{
|
||||
QProcess process(parent);
|
||||
process.start(Utils::getCorePath(), { "-version" });
|
||||
|
||||
if (!process.waitForStarted()) {
|
||||
throw std::runtime_error("核心无法启动");
|
||||
}
|
||||
|
||||
if (!process.waitForFinished()) {
|
||||
throw std::runtime_error("核心运行超时");
|
||||
}
|
||||
|
||||
// 获取错误输出
|
||||
QByteArray errorOutput = process.readAllStandardError();
|
||||
if (!errorOutput.isEmpty()) {
|
||||
throw std::runtime_error(errorOutput);
|
||||
}
|
||||
|
||||
// 获取标准输出
|
||||
QString output = process.readAllStandardOutput();
|
||||
|
||||
const QString prefix("ZJU Connect v");
|
||||
if (!output.startsWith(prefix)) {
|
||||
throw std::runtime_error("无法解析核心版本号");
|
||||
}
|
||||
return output.mid(prefix.size()).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#ifndef PROFILEMANAGER_H
|
||||
#define PROFILEMANAGER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class ProfileManager
|
||||
{
|
||||
public:
|
||||
ProfileManager();
|
||||
|
||||
QStringList listProfiles() const;
|
||||
|
||||
QString activeProfile() const;
|
||||
|
||||
bool setActiveProfile(const QString &profileId) const;
|
||||
|
||||
QString profilePath(const QString &profileId) const;
|
||||
|
||||
QString createProfile(const QString &requestedName, const QString &sourcePath = QString()) const;
|
||||
|
||||
bool renameProfile(const QString &oldId, const QString &newId) const;
|
||||
|
||||
bool removeProfile(const QString &profileId) const;
|
||||
|
||||
QString normalizeProfileId(const QString &name) const;
|
||||
|
||||
bool autoStartEnabled() const;
|
||||
|
||||
void setAutoStartEnabled(bool enabled) const;
|
||||
|
||||
bool silentStartEnabled() const;
|
||||
|
||||
void setSilentStartEnabled(bool enabled) const;
|
||||
|
||||
private:
|
||||
QString configRootPath;
|
||||
QString profilesPath;
|
||||
QString statePath;
|
||||
QString defaultProfilePath;
|
||||
|
||||
QString ensureUniqueProfileId(const QString &baseId) const;
|
||||
|
||||
void ensureStorage() const;
|
||||
};
|
||||
|
||||
#endif //PROFILEMANAGER_H
|
||||
@@ -1,52 +0,0 @@
|
||||
#include <QProcess>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace Utils {
|
||||
void setDeviceTrust(QObject *parent, const QString &protocol, const QString &server, int port, const QString &profileId, bool trust)
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
if (!protocol.isEmpty())
|
||||
{
|
||||
args.append("-protocol");
|
||||
args.append(protocol);
|
||||
}
|
||||
|
||||
if (!server.isEmpty())
|
||||
{
|
||||
args.append("-server");
|
||||
args.append(server);
|
||||
}
|
||||
|
||||
if (port != 0)
|
||||
{
|
||||
args.append("-port");
|
||||
args.append(QString::number(port));
|
||||
}
|
||||
|
||||
args.append("-client-data-file");
|
||||
args.append(Utils::getClientDataPath(profileId));
|
||||
|
||||
args.append(trust ? "-trust-device" : "-untrust-device");
|
||||
|
||||
QProcess process(parent);
|
||||
process.start(Utils::getCorePath(), args);
|
||||
|
||||
if (!process.waitForStarted())
|
||||
{
|
||||
throw std::runtime_error("核心无法启动");
|
||||
}
|
||||
|
||||
if (!process.waitForFinished())
|
||||
{
|
||||
throw std::runtime_error("核心运行超时");
|
||||
}
|
||||
|
||||
QByteArray errorOutput = process.readAllStandardError();
|
||||
if ((trust && !errorOutput.contains("Device trusted successfully")) || (!trust && !errorOutput.contains("Device untrusted successfully")))
|
||||
{
|
||||
throw std::runtime_error(errorOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
520
utils/utils.cpp
520
utils/utils.cpp
@@ -1,520 +0,0 @@
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QApplication>
|
||||
#include <QNetworkInterface>
|
||||
#include <QTextCodec>
|
||||
#include <QSettings>
|
||||
#include <QProcess>
|
||||
#include <QtSystemDetection>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#elif defined(Q_OS_UNIX)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "utils.h"
|
||||
#include "profilemanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QString profileIdFromConfigPath(const QString &configPath)
|
||||
{
|
||||
QFileInfo info(configPath);
|
||||
if (!info.exists() || info.suffix() != "ini")
|
||||
{
|
||||
return "custom";
|
||||
}
|
||||
|
||||
if (info.fileName() == "state.ini")
|
||||
{
|
||||
return "custom";
|
||||
}
|
||||
|
||||
QDir parentDir = info.dir();
|
||||
if (parentDir.dirName() != "profiles")
|
||||
{
|
||||
return "custom";
|
||||
}
|
||||
|
||||
return info.baseName();
|
||||
}
|
||||
}
|
||||
|
||||
QString Utils::consoleOutputToQString(const QByteArray &byteArray)
|
||||
{
|
||||
static QString codeName = "";
|
||||
if (codeName == "UTF-8")
|
||||
{
|
||||
return QTextCodec::codecForName("UTF-8")->toUnicode(byteArray);;
|
||||
}
|
||||
else if (codeName == "GBK")
|
||||
{
|
||||
return QTextCodec::codecForName("GBK")->toUnicode(byteArray);;
|
||||
}
|
||||
else if (codeName == "locale")
|
||||
{
|
||||
return QString::fromLocal8Bit(byteArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextCodec *utf8Codec = QTextCodec::codecForName("UTF-8");
|
||||
QString utf8Str = utf8Codec->toUnicode(byteArray);
|
||||
QByteArray utf8ByteArrayBack = utf8Codec->fromUnicode(utf8Str);
|
||||
|
||||
QTextCodec *gbkCodec = QTextCodec::codecForName("GBK");
|
||||
QString gbkStr = gbkCodec->toUnicode(byteArray);
|
||||
QByteArray gbkByteArrayBack = gbkCodec->fromUnicode(gbkStr);
|
||||
|
||||
if (utf8ByteArrayBack == byteArray && gbkByteArrayBack != byteArray)
|
||||
{
|
||||
codeName = "UTF-8";
|
||||
return utf8Str;
|
||||
}
|
||||
else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack != byteArray)
|
||||
{
|
||||
codeName = "GBK";
|
||||
return gbkStr;
|
||||
}
|
||||
else if (gbkByteArrayBack == byteArray && utf8ByteArrayBack == byteArray)
|
||||
{
|
||||
return utf8Str;
|
||||
}
|
||||
else
|
||||
{
|
||||
codeName = "locale";
|
||||
return QString::fromLocal8Bit(byteArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Utils::getArgValue(const QStringList &args, const QString &key)
|
||||
{
|
||||
const QString prefix = key + "=";
|
||||
for (int i = 0; i < args.size(); ++i)
|
||||
{
|
||||
const QString &arg = args.at(i);
|
||||
if (arg == key)
|
||||
{
|
||||
if (i + 1 < args.size())
|
||||
{
|
||||
return args.at(i + 1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
if (arg.startsWith(prefix))
|
||||
{
|
||||
return arg.mid(prefix.size());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void Utils::setWidgetFixedWhenHidden(QWidget *widget)
|
||||
{
|
||||
QSizePolicy originPolicy = widget->sizePolicy();
|
||||
originPolicy.setRetainSizeWhenHidden(true);
|
||||
widget->setSizePolicy(originPolicy);
|
||||
}
|
||||
|
||||
void Utils::showAboutMessageBox(QWidget *parent)
|
||||
{
|
||||
QMessageBox messageBox(parent);
|
||||
messageBox.setWindowTitle("关于");
|
||||
messageBox.setTextFormat(Qt::RichText);
|
||||
QString aboutText = QApplication::applicationDisplayName() + " " + QApplication::applicationVersion() +
|
||||
"<br>改进的 ZJU-Connect 图形界面" +
|
||||
"<br>作者:<a href='https://github.com/chenx-dust'>Chenx Dust</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/" + REPO_NAME + "'>https://github.com/" + REPO_NAME + "</a>" +
|
||||
"<br><br><b>致谢:</b>" +
|
||||
"<br><br>ZJU-Connect-for-Windows" +
|
||||
"<br>基于 Qt 编写的 ZJU 网络客户端" +
|
||||
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/Mythologyli/ZJU-Connect-for-Windows'>https://github.com/Mythologyli/ZJU-Connect-for-Windows</a>" +
|
||||
"<br><br>zju-connect" +
|
||||
"<br>ZJU RVPN 客户端的 Go 语言实现" +
|
||||
"<br>作者:<a href='https://myth.cx'>Myth</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/Mythologyli/zju-connect'>https://github.com/Mythologyli/zju-connect</a>" +
|
||||
"<br><br>EasierConnect" +
|
||||
"<br>EasyConnect 客户端的开源实现" +
|
||||
"<br>作者:<a href='https://github.com/lyc8503'>lyc8503</a>" +
|
||||
"<br>项目主页:<a href='https://github.com/lyc8503/EasierConnect'>https://github.com/lyc8503/EasierConnect</a>";
|
||||
messageBox.setText(aboutText);
|
||||
messageBox.setIconPixmap(QPixmap(":/resource/icon.png").scaled(
|
||||
100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation
|
||||
));
|
||||
messageBox.exec();
|
||||
}
|
||||
|
||||
QString Utils::getIpv4Address(const QString &interfaceName)
|
||||
{
|
||||
auto interfaces = QNetworkInterface::allInterfaces();
|
||||
for (auto &singleInterface: interfaces)
|
||||
{
|
||||
if (singleInterface.humanReadableName() == interfaceName)
|
||||
{
|
||||
auto addresses =
|
||||
singleInterface.addressEntries();
|
||||
for (auto &address: addresses)
|
||||
{
|
||||
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
return address.ip().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
QString macOSAppBundlePath()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
QDir dir = QDir(QCoreApplication::applicationDirPath());
|
||||
dir.cdUp();
|
||||
dir.cdUp();
|
||||
QString absolutePath = dir.absolutePath();
|
||||
// absolutePath will contain a "/" at the end,
|
||||
// but we want the clean path to the .app bundle
|
||||
if (absolutePath.length() > 0 && absolutePath.right(1) == "/")
|
||||
{
|
||||
absolutePath.chop(1);
|
||||
}
|
||||
return absolutePath;
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString macOSAppBundleName()
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
QString bundlePath = macOSAppBundlePath();
|
||||
QFileInfo fileInfo(bundlePath);
|
||||
return fileInfo.baseName();
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString nativeAppPath()
|
||||
{
|
||||
return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
|
||||
}
|
||||
|
||||
bool Utils::isRunningAsAdmin()
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
BOOL isAdmin = FALSE;
|
||||
PSID adminGroup = nullptr;
|
||||
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
|
||||
|
||||
if (AllocateAndInitializeSid(&ntAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroup))
|
||||
{
|
||||
CheckTokenMembership(nullptr, adminGroup, &isAdmin);
|
||||
FreeSid(adminGroup);
|
||||
}
|
||||
return isAdmin;
|
||||
#elif defined(Q_OS_UNIX)
|
||||
return geteuid() == 0;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Utils::relaunchAsAdmin()
|
||||
{
|
||||
QString program = QCoreApplication::applicationFilePath();
|
||||
QStringList args = QCoreApplication::arguments();
|
||||
if (!args.isEmpty())
|
||||
{
|
||||
args.removeFirst();
|
||||
}
|
||||
|
||||
if (!args.contains("--connect"))
|
||||
{
|
||||
args << "--connect";
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
QStringList quotedArgs;
|
||||
for (const QString &arg : args)
|
||||
{
|
||||
if (arg.contains(' '))
|
||||
{
|
||||
quotedArgs << "\"" + arg + "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
quotedArgs << arg;
|
||||
}
|
||||
}
|
||||
QString arguments = quotedArgs.join(" ");
|
||||
HINSTANCE res = ShellExecuteW(
|
||||
nullptr,
|
||||
L"runas",
|
||||
reinterpret_cast<LPCWSTR>(program.utf16()),
|
||||
reinterpret_cast<LPCWSTR>(arguments.utf16()),
|
||||
nullptr,
|
||||
SW_SHOWNORMAL
|
||||
);
|
||||
|
||||
return reinterpret_cast<INT_PTR>(res) > 32;
|
||||
#else
|
||||
Q_UNUSED(program)
|
||||
Q_UNUSED(args)
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Utils::setAutoStart(bool enable)
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
QSettings autoStartSettings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)",
|
||||
QSettings::NativeFormat);
|
||||
if (enable)
|
||||
{
|
||||
autoStartSettings.setValue(
|
||||
QApplication::applicationName(),
|
||||
nativeAppPath()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
autoStartSettings.remove(QApplication::applicationName());
|
||||
}
|
||||
autoStartSettings.sync();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
{
|
||||
QStringList args;
|
||||
args << "-e" << "tell application \"System Events\" to delete login item \"" + macOSAppBundleName() + "\"";
|
||||
qDebug() << args;
|
||||
|
||||
QProcess process;
|
||||
process.start("osascript", args);
|
||||
process.waitForFinished();
|
||||
QString error = process.readAllStandardError();
|
||||
qDebug() << process.readAllStandardOutput();
|
||||
qDebug() << error;
|
||||
if (!enable && process.error() != QProcess::UnknownError)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项:" + process.errorString());
|
||||
return;
|
||||
}
|
||||
if (!enable && process.exitCode() != 0)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除登录项:" + error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (enable)
|
||||
{
|
||||
QStringList args;
|
||||
args << "-e" << "tell application \"System Events\" to make login item at end with properties {path:\"" +
|
||||
macOSAppBundlePath() + "\", hidden:false}";
|
||||
qDebug() << args;
|
||||
|
||||
QProcess process;
|
||||
process.start("osascript", args);
|
||||
process.waitForFinished();
|
||||
qDebug() << process.readAllStandardOutput();
|
||||
qDebug() << process.readAllStandardError();
|
||||
if (process.error() != QProcess::UnknownError)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项:" + process.errorString());
|
||||
return;
|
||||
}
|
||||
if (process.exitCode() != 0)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建登录项:" + process.readAllStandardError());
|
||||
return;
|
||||
}
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QString autostartPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/autostart/";
|
||||
QDir dir(autostartPath);
|
||||
QString desktopFilePath = autostartPath + QApplication::applicationName() + ".desktop";
|
||||
QFile desktopFile(desktopFilePath);
|
||||
|
||||
if (dir.exists() && desktopFile.exists())
|
||||
{
|
||||
if (!desktopFile.remove())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "取消开机自启动失败", "无法删除 .desktop 文件:" + desktopFilePath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (enable)
|
||||
{
|
||||
if (!dir.exists())
|
||||
{
|
||||
if (!dir.mkpath("."))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 autostart 目录:" + autostartPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!desktopFile.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "设置开机自启动失败", "无法创建 .desktop 文件:" + desktopFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream out(&desktopFile);
|
||||
out << "[Desktop Entry]\n";
|
||||
out << "Type=Application\n";
|
||||
out << "Name=" << QApplication::applicationName() << "\n";
|
||||
out << "Exec=" << nativeAppPath() << "\n";
|
||||
out << "X-GNOME-Autostart-enabled=true\n";
|
||||
desktopFile.close();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Utils::credentialCheck(const QString &username, const QString &password)
|
||||
{
|
||||
if (username.isEmpty() || password.isEmpty())
|
||||
{
|
||||
int status = QMessageBox::warning(nullptr, "警告", "账号或密码为空!\n\n是否继续?", QMessageBox::Ok, QMessageBox::Cancel);
|
||||
return status == QMessageBox::Ok;
|
||||
}
|
||||
|
||||
bool asciiOnly = true;
|
||||
for (QChar c: username)
|
||||
{
|
||||
if (c.unicode() > 127)
|
||||
{
|
||||
asciiOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (QChar c: password)
|
||||
{
|
||||
if (c.unicode() > 127)
|
||||
{
|
||||
asciiOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!asciiOnly)
|
||||
{
|
||||
int status = QMessageBox::warning(nullptr, "警告", "账号或密码存在非 ASCII 字符!\n建议检查输入法设置。\n\n是否继续?", QMessageBox::Ok, QMessageBox::Cancel);
|
||||
return status == QMessageBox::Ok;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Utils::clearClientData(const QString &profileId)
|
||||
{
|
||||
QFile::remove(getClientDataPath(profileId));
|
||||
}
|
||||
|
||||
QString Utils::getClientDataPath(const QString &profileId)
|
||||
{
|
||||
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
QDir dir(defaultPath);
|
||||
if (!dir.exists())
|
||||
{
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
QDir profileDir(dir.filePath("profiles/" + profileId));
|
||||
if (!profileDir.exists())
|
||||
{
|
||||
profileDir.mkpath(".");
|
||||
}
|
||||
|
||||
QFile clientDataFile(profileDir.filePath("client-data.json"));
|
||||
if (!clientDataFile.exists() && clientDataFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
clientDataFile.write("{}");
|
||||
clientDataFile.close();
|
||||
}
|
||||
|
||||
return profileDir.filePath("client-data.json");
|
||||
}
|
||||
|
||||
QString Utils::getLogFilePath()
|
||||
{
|
||||
QString logPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
QDir logDir(logPath);
|
||||
if (!logDir.exists())
|
||||
{
|
||||
logDir.mkpath(".");
|
||||
}
|
||||
return logDir.filePath("zjuconnect.log");
|
||||
}
|
||||
|
||||
void Utils::resetDefaultSettings(QSettings& settings)
|
||||
{
|
||||
settings.setValue("Credential/Username", "");
|
||||
settings.setValue("Credential/Password", "");
|
||||
settings.setValue("Credential/TOTPSecret", "");
|
||||
|
||||
settings.setValue("Common/ConnectAfterStart", false);
|
||||
settings.setValue("Common/CheckUpdateAfterStart", false);
|
||||
settings.setValue("Common/AutoSetProxy", false);
|
||||
settings.setValue("Common/ReconnectTime", 1);
|
||||
settings.setValue("Common/AutoReconnect", false);
|
||||
settings.setValue("Common/SystemProxyBypass", "");
|
||||
|
||||
|
||||
settings.setValue("ZJUConnect/ServerAddress", "trust.hitsz.edu.cn");
|
||||
settings.setValue("ZJUConnect/ServerPort", 443);
|
||||
settings.setValue("ZJUConnect/DNS", "");
|
||||
settings.setValue("ZJUConnect/DNSAuto", true);
|
||||
settings.setValue("ZJUConnect/SecondaryDNS", "");
|
||||
settings.setValue("ZJUConnect/DNSTTL", 3600);
|
||||
settings.setValue("ZJUConnect/SOCKS5Port", 11080);
|
||||
settings.setValue("ZJUConnect/HTTPPort", 11081);
|
||||
settings.setValue("ZJUConnect/ShadowsocksURL", "");
|
||||
settings.setValue("ZJUConnect/DialDirectProxy", "");
|
||||
settings.setValue("ZJUConnect/UpdateBestNodesInterval", 300);
|
||||
|
||||
settings.setValue("ZJUConnect/Protocol", "atrust");
|
||||
settings.setValue("ZJUConnect/LoginDomain", "hitcas");
|
||||
settings.setValue("ZJUConnect/AuthType", "cas");
|
||||
settings.setValue("ZJUConnect/LoginURL", "");
|
||||
settings.setValue("ZJUConnect/PhoneCountryCode", "86");
|
||||
settings.setValue("ZJUConnect/PhoneNumber", "");
|
||||
|
||||
settings.setValue("ZJUConnect/MultiLine", false);
|
||||
settings.setValue("ZJUConnect/KeepAlive", false);
|
||||
settings.setValue("ZJUConnect/KeepAliveURL", "");
|
||||
settings.setValue("ZJUConnect/BindInterface", "");
|
||||
settings.setValue("ZJUConnect/OutsideAccess", false);
|
||||
|
||||
settings.setValue("ZJUConnect/SkipDomainResource", false);
|
||||
settings.setValue("ZJUConnect/DisableServerConfig", false);
|
||||
settings.setValue("ZJUConnect/ProxyAll", false);
|
||||
|
||||
settings.setValue("ZJUConnect/DisableZJUDNS", false);
|
||||
settings.setValue("ZJUConnect/ZJUDefault", false);
|
||||
settings.setValue("ZJUConnect/Debug", false);
|
||||
|
||||
settings.setValue("ZJUConnect/TUNMode", false);
|
||||
settings.setValue("ZJUConnect/AddRoute", false);
|
||||
settings.setValue("ZJUConnect/DNSHijack", false);
|
||||
settings.setValue("ZJUConnect/FakeIP", false);
|
||||
settings.setValue("ZJUConnect/TCPTunnelMode", false);
|
||||
settings.setValue("ZJUConnect/AutoDetectInterface", false);
|
||||
|
||||
|
||||
settings.setValue("ZJUConnect/TCPPortForwarding", "");
|
||||
settings.setValue("ZJUConnect/UDPPortForwarding", "");
|
||||
settings.setValue("ZJUConnect/CustomDNS", "");
|
||||
settings.setValue("ZJUConnect/CustomProxyDomain", "");
|
||||
settings.setValue("ZJUConnect/ExtraArguments", "");
|
||||
|
||||
settings.setValue("Common/ConfigVersion", Utils::CONFIG_VERSION);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QNetworkReply>
|
||||
#include <QWidget>
|
||||
#include <QSettings>
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
const inline QString REPO_NAME("chenx-dust/EZ4Connect");
|
||||
const inline QString CORE_REPO_NAME("Mythologyli/zju-connect");
|
||||
|
||||
const inline QString APP_NAME("EZ4Connect");
|
||||
|
||||
constexpr inline int CONFIG_VERSION = 8;
|
||||
|
||||
QString consoleOutputToQString(const QByteArray &byteArray);
|
||||
|
||||
void setWidgetFixedWhenHidden(QWidget *widget);
|
||||
|
||||
void showAboutMessageBox(QWidget *parent = nullptr);
|
||||
|
||||
bool isSystemProxySet(int http_port = -1, int socks_port = -1);
|
||||
|
||||
void setSystemProxy(int http_port, int socks_port, const QString &bypass);
|
||||
|
||||
void clearSystemProxy();
|
||||
|
||||
QString getIpv4Address(const QString &interfaceName);
|
||||
|
||||
void setAutoStart(bool enable);
|
||||
|
||||
bool credentialCheck(const QString &username, const QString &password);
|
||||
|
||||
void resetDefaultSettings(QSettings &settings);
|
||||
|
||||
QString getCorePath();
|
||||
|
||||
QString checkCoreVersion(QObject *parent);
|
||||
|
||||
bool isRunningAsAdmin();
|
||||
|
||||
bool relaunchAsAdmin();
|
||||
|
||||
void clearClientData(const QString &profileId);
|
||||
|
||||
QString getClientDataPath(const QString &profileId);
|
||||
|
||||
QString getLogFilePath();
|
||||
|
||||
QString getArgValue(const QStringList &args, const QString &key);
|
||||
|
||||
void setDeviceTrust(QObject *parent, const QString &protocol, const QString &server, int port, const QString &profileId, bool trust);
|
||||
}
|
||||
|
||||
#endif //UTILS_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user