update: several refactors and Import window redesign

This commit is contained in:
Qv2ray-dev 2020-05-17 22:04:03 +08:00
parent fc71624213
commit 31f6e1389f
17 changed files with 356 additions and 351 deletions

View File

@ -1 +1 @@
5460 5461

View File

@ -66,9 +66,9 @@ namespace Qv2ray::common
request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, ua); request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, ua);
} }
QByteArray QvHttpRequestHelper::Get(const QString &url) QByteArray QvHttpRequestHelper::Get(const QUrl &url)
{ {
request.setUrl({ url }); request.setUrl(url);
setAccessManagerAttributes(accessManager); setAccessManagerAttributes(accessManager);
auto _reply = accessManager.get(request); auto _reply = accessManager.get(request);
// //

View File

@ -33,7 +33,11 @@ namespace Qv2ray::common
~QvHttpRequestHelper(); ~QvHttpRequestHelper();
// get // get
void AsyncGet(const QString &url); void AsyncGet(const QString &url);
QByteArray Get(const QString &url); QByteArray Get(const QUrl &url);
QByteArray Get(const QString &url)
{
return Get(QUrl{ url });
}
signals: signals:
void OnRequestFinished(QByteArray &data); void OnRequestFinished(QByteArray &data);

View File

@ -1,5 +1,7 @@
#include "ConnectionIO.hpp" #include "ConnectionIO.hpp"
#include "Serialization.hpp"
#include "common/HTTPRequestHelper.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
namespace Qv2ray::core::connection namespace Qv2ray::core::connection
@ -8,14 +10,7 @@ namespace Qv2ray::core::connection
{ {
CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex) CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex)
{ {
QFile source(sourceFilePath); auto root = CONFIGROOT(JsonFromString(StringFromFile(sourceFilePath)));
if (!source.exists())
{
LOG(MODULE_FILEIO, "Trying to import from an non-existing file.") return CONFIGROOT();
}
auto root = CONFIGROOT(JsonFromString(StringFromFile(source)));
if (!importComplex) if (!importComplex)
{ {
@ -29,5 +24,27 @@ namespace Qv2ray::core::connection
root.remove("dns"); root.remove("dns");
return root; return root;
} }
QMultiHash<QString, CONFIGROOT> GetConnectionConfigFromSubscription(const QUrl &subscriptionUrl, const QString &groupName)
{
QMultiHash<QString, CONFIGROOT> subscriptionContent;
QvHttpRequestHelper helper;
const auto data = helper.Get(subscriptionUrl);
auto subscriptionLines = SplitLines(TryDecodeSubscriptionString(data));
for (const auto &line : subscriptionLines)
{
QString __alias;
QString __errMessage;
// Assign a group name, to pass the name check.
QString __groupName = groupName;
auto connectionConfigMap = ConvertConfigFromString(line.trimmed(), &__alias, &__errMessage, &__groupName);
if (!__errMessage.isEmpty())
LOG(MODULE_SUBSCRIPTION, "Error: " + __errMessage)
for (const auto &val : connectionConfigMap)
{
subscriptionContent.insert(connectionConfigMap.key(val), val);
}
}
return subscriptionContent;
}
} // namespace ConnectionIO } // namespace ConnectionIO
} // namespace Qv2ray::core::connection } // namespace Qv2ray::core::connection

View File

@ -6,6 +6,7 @@ namespace Qv2ray::core::connection
{ {
// File Protocol // File Protocol
CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex); CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex);
QMultiHash<QString, CONFIGROOT> GetConnectionConfigFromSubscription(const QUrl &subscriptionUrl, const QString &groupName);
} // namespace ConnectionIO } // namespace ConnectionIO
} // namespace Qv2ray::core::connection } // namespace Qv2ray::core::connection

View File

@ -1,7 +1,6 @@
#include "Serialization.hpp" #include "Serialization.hpp"
#include "Generation.hpp" #include "Generation.hpp"
#include "common/QvHelpers.hpp"
#include "components/plugins/QvPluginHost.hpp" #include "components/plugins/QvPluginHost.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
@ -49,7 +48,7 @@ namespace Qv2ray::core::connection
{ {
CONFIGROOT root; CONFIGROOT root;
auto outbound = GenerateOutboundEntry(val.first, OUTBOUNDSETTING(val.second), {}); auto outbound = GenerateOutboundEntry(val.first, OUTBOUNDSETTING(val.second), {});
root.insert("outbounds", QJsonArray{ outbound }); QJsonIO::SetValue(root, outbound, "outbounds", 0);
connectionConf.insert(key, root); connectionConf.insert(key, root);
} }
} }
@ -108,14 +107,5 @@ namespace Qv2ray::core::connection
return sharelink; return sharelink;
} }
QString DecodeSubscriptionString(const QByteArray &arr)
{
// String may start with: vmess:// and ss://
// We only process vmess:// here
// Some subscription providers may use plain vmess:// saperated by
// lines But others may use base64 of above.
auto result = QString::fromUtf8(arr).trimmed();
return result.contains("://") ? result : SafeBase64Decode(result);
}
} // namespace Serialization } // namespace Serialization
} // namespace Qv2ray::core::connection } // namespace Qv2ray::core::connection

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
#include "common/QvHelpers.hpp"
namespace Qv2ray::core::connection namespace Qv2ray::core::connection
{ {
@ -15,7 +16,11 @@ namespace Qv2ray::core::connection
const inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); const inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)");
// //
// General // General
QString DecodeSubscriptionString(const QByteArray &arr); inline const QString TryDecodeSubscriptionString(const QByteArray &arr)
{
auto result = QString::fromUtf8(arr).trimmed();
return result.contains("://") ? result : SafeBase64Decode(result);
}
QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage,
QString *newGroupName = nullptr); QString *newGroupName = nullptr);
const QString ConvertConfigToString(const ConnectionGroupPair &id, bool isSip002 = false); const QString ConvertConfigToString(const ConnectionGroupPair &id, bool isSip002 = false);

View File

@ -66,7 +66,6 @@ namespace Qv2ray::core::connection
CONFIGROOT Deserialize(const QString &vmessStr, QString *alias, QString *errMessage) CONFIGROOT Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
{ {
#define default CONFIGROOT() #define default CONFIGROOT()
LOG(MODULE_SETTINGS, "Trying to convert from a vmess string.")
QString vmess = vmessStr; QString vmess = vmessStr;
if (vmess.trimmed() != vmess) if (vmess.trimmed() != vmess)

View File

@ -66,7 +66,6 @@ namespace Qv2ray::core::handlers
connect(kernelHandler, &KernelInstanceHandler::OnDisconnected, this, &QvConfigHandler::OnDisconnected); connect(kernelHandler, &KernelInstanceHandler::OnDisconnected, this, &QvConfigHandler::OnDisconnected);
// //
tcpingHelper = new QvTCPingHelper(5, this); tcpingHelper = new QvTCPingHelper(5, this);
httpHelper = new QvHttpRequestHelper(this);
connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p); connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p);
// //
// Save per 1 minutes. // Save per 1 minutes.
@ -369,7 +368,6 @@ namespace Qv2ray::core::handlers
{ {
LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor") LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor")
delete kernelHandler; delete kernelHandler;
delete httpHelper;
CHSaveConfigData(); CHSaveConfigData();
} }
@ -465,57 +463,33 @@ namespace Qv2ray::core::handlers
bool QvConfigHandler::UpdateSubscription(const GroupId &id) bool QvConfigHandler::UpdateSubscription(const GroupId &id)
{ {
CheckGroupExistanceEx(id, false); CheckGroupExistanceEx(id, false);
if (isHttpRequestInProgress || !groups[id].isSubscription) if (!groups[id].isSubscription)
{ {
return false; return false;
} }
isHttpRequestInProgress = true; return CHUpdateSubscription_p(id, groups[id].subscriptionOption.address);
auto data = httpHelper->Get(groups[id].subscriptionOption.address);
isHttpRequestInProgress = false;
return CHUpdateSubscription_p(id, data);
} }
bool QvConfigHandler::CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData) bool QvConfigHandler::CHUpdateSubscription_p(const GroupId &id, const QString &url)
{ {
CheckGroupExistanceEx(id, false); CheckGroupExistanceEx(id, false);
if (!groups.contains(id)) if (!groups.contains(id))
{ {
return false; return false;
} }
QMultiHash<QString, CONFIGROOT> allSubscriptionConnections;
//
//
// //
// ====================================================================================== Begin reading subscription // ====================================================================================== Begin reading subscription
{ auto _newConnections = GetConnectionConfigFromSubscription(url, GetDisplayName(id));
auto subscriptionLines = SplitLines(DecodeSubscriptionString(subscriptionData)); if (_newConnections.count() < 5)
for (const auto &line : subscriptionLines)
{
QString __alias;
QString __errMessage;
// Assign a group name, to pass the name check.
QString __groupName = GetDisplayName(id);
auto connectionConfigMap = ConvertConfigFromString(line.trimmed(), &__alias, &__errMessage, &__groupName);
if (!__errMessage.isEmpty())
LOG(MODULE_SUBSCRIPTION, "Error: " + __errMessage)
for (const auto &val : connectionConfigMap)
{
allSubscriptionConnections.insert(connectionConfigMap.key(val), val);
}
}
if (allSubscriptionConnections.count() < 5)
{ {
LOG(MODULE_SUBSCRIPTION, "Find a subscription with less than 5 connections.") LOG(MODULE_SUBSCRIPTION, "Find a subscription with less than 5 connections.")
if (QvMessageBoxAsk(nullptr, tr("Update Subscription"), if (QvMessageBoxAsk(
tr("%1 entrie(s) have been found from the subscription source, do you want to continue?") nullptr, tr("Update Subscription"),
.arg(allSubscriptionConnections.count())) != QMessageBox::Yes) tr("%1 entrie(s) have been found from the subscription source, do you want to continue?").arg(_newConnections.count())) !=
QMessageBox::Yes)
return false; return false;
} }
}
// ====================================================================================== End reading subscription
//
//
// //
// ====================================================================================== Begin Connection Data Storage // ====================================================================================== Begin Connection Data Storage
// Anyway, we try our best to preserve the connection id. // Anyway, we try our best to preserve the connection id.
@ -540,9 +514,9 @@ namespace Qv2ray::core::handlers
auto originalConnectionIdList = groups[id].connections; auto originalConnectionIdList = groups[id].connections;
groups[id].connections.clear(); groups[id].connections.clear();
// //
for (const auto &config : allSubscriptionConnections) for (const auto &config : _newConnections)
{ {
const auto _alias = allSubscriptionConnections.key(config); const auto _alias = _newConnections.key(config);
QString errMessage; QString errMessage;
if (!errMessage.isEmpty()) if (!errMessage.isEmpty())
@ -562,7 +536,7 @@ namespace Qv2ray::core::handlers
LOG(MODULE_CORE_HANDLER, "Reused connection id from name: " + _alias) LOG(MODULE_CORE_HANDLER, "Reused connection id from name: " + _alias)
auto _conn = nameMap.take(_alias); auto _conn = nameMap.take(_alias);
groups[id].connections << _conn; groups[id].connections << _conn;
UpdateConnection(_conn, config); UpdateConnection(_conn, config, true);
// Remove Connection Id from the list. // Remove Connection Id from the list.
originalConnectionIdList.removeAll(_conn); originalConnectionIdList.removeAll(_conn);
typeMap.remove(typeMap.key(_conn)); typeMap.remove(typeMap.key(_conn));
@ -573,7 +547,7 @@ namespace Qv2ray::core::handlers
auto _conn = typeMap.take(outboundData); auto _conn = typeMap.take(outboundData);
groups[id].connections << _conn; groups[id].connections << _conn;
// Update Connection Properties // Update Connection Properties
UpdateConnection(_conn, config); UpdateConnection(_conn, config, true);
RenameConnection(_conn, _alias); RenameConnection(_conn, _alias);
// Remove Connection Id from the list. // Remove Connection Id from the list.
originalConnectionIdList.removeAll(_conn); originalConnectionIdList.removeAll(_conn);
@ -583,7 +557,7 @@ namespace Qv2ray::core::handlers
{ {
// New connection id is required since nothing matched found... // New connection id is required since nothing matched found...
LOG(MODULE_CORE_HANDLER, "Generated new connection id for connection: " + _alias) LOG(MODULE_CORE_HANDLER, "Generated new connection id for connection: " + _alias)
CreateConnection(config, _alias, id); CreateConnection(config, _alias, id, true);
} }
// ====================================================================================== End guessing new ConnectionId // ====================================================================================== End guessing new ConnectionId
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
#include "common/HTTPRequestHelper.hpp"
#include "components/latency/QvTCPing.hpp" #include "components/latency/QvTCPing.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/connection/ConnectionIO.hpp" #include "core/connection/ConnectionIO.hpp"
@ -152,7 +151,7 @@ namespace Qv2ray::core::handlers
void timerEvent(QTimerEvent *event) override; void timerEvent(QTimerEvent *event) override;
private: private:
bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData); bool CHUpdateSubscription_p(const GroupId &id, const QString &url);
private: private:
int saveTimerId; int saveTimerId;
@ -163,8 +162,6 @@ namespace Qv2ray::core::handlers
QHash<ConnectionId, CONFIGROOT> connectionRootCache; QHash<ConnectionId, CONFIGROOT> connectionRootCache;
private: private:
QvHttpRequestHelper *httpHelper;
bool isHttpRequestInProgress = false;
QvTCPingHelper *tcpingHelper; QvTCPingHelper *tcpingHelper;
KernelInstanceHandler *kernelHandler; KernelInstanceHandler *kernelHandler;
}; };

View File

@ -257,7 +257,7 @@ GroupManager::~GroupManager()
void GroupManager::on_addGroupButton_clicked() void GroupManager::on_addGroupButton_clicked()
{ {
auto const key = tr("New Group") + " - " + GenerateRandomString(5); auto const key = tr("New Group") + " - " + GenerateRandomString(5);
auto id = ConnectionManager->CreateGroup(key, true); auto id = ConnectionManager->CreateGroup(key, false);
// //
auto item = new QListWidgetItem(key); auto item = new QListWidgetItem(key);
item->setData(Qt::UserRole, id.toString()); item->setData(Qt::UserRole, id.toString());
@ -266,7 +266,7 @@ void GroupManager::on_addGroupButton_clicked()
void GroupManager::on_updateButton_clicked() void GroupManager::on_updateButton_clicked()
{ {
if (QvMessageBoxAsk(this, tr("Reload Subscription"), tr("Would you like to reload the subscription?")) == QMessageBox::Yes) if (QvMessageBoxAsk(this, tr("Update Subscription"), tr("Would you like to update the subscription?")) == QMessageBox::Yes)
{ {
this->setEnabled(false); this->setEnabled(false);
ConnectionManager->UpdateSubscription(currentGroupId); ConnectionManager->UpdateSubscription(currentGroupId);

View File

@ -105,7 +105,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/add.png</normaloff>:/assets/icons/ui_light/add.png</iconset> <normaloff>:/assets/icons/ui_light/add.png</normaloff>:/assets/icons/ui_light/add.png</iconset>
</property> </property>
</widget> </widget>
@ -125,7 +125,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset> <normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset>
</property> </property>
</widget> </widget>
@ -227,6 +227,9 @@
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout_3"> <layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
@ -321,26 +324,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Connection Settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<layout class="QGridLayout" name="connectionSettingsLayout"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Route Settings</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QGridLayout" name="routeSettingsLayout"/>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -352,7 +335,7 @@
<tabstop>updateIntervalSB</tabstop> <tabstop>updateIntervalSB</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../resources.qrc"/> <include location="../../../resources.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@ -20,12 +20,22 @@
#include <QJsonObject> #include <QJsonObject>
#include <QThread> #include <QThread>
constexpr auto LINK_PAGE = 0;
constexpr auto QRCODE_PAGE = 1;
constexpr auto MANUAL_PAGE = 2;
constexpr auto ADVANCED_PAGE = 3;
ImportConfigWindow::ImportConfigWindow(QWidget *parent) : QDialog(parent) ImportConfigWindow::ImportConfigWindow(QWidget *parent) : QDialog(parent)
{ {
setupUi(this); setupUi(this);
// nameTxt->setText(tr("My Connection Imported at: ") + QDateTime::currentDateTime().toString("MM-dd hh:mm")); nameTxt->setText(tr("New Connection") + QDateTime::currentDateTime().toString("MM-dd hh:mm"));
QvMessageBusConnect(ImportConfigWindow); QvMessageBusConnect(ImportConfigWindow);
RESTORE_RUNTIME_CONFIG(screenShotHideQv2ray, hideQv2rayCB->setChecked) RESTORE_RUNTIME_CONFIG(screenShotHideQv2ray, hideQv2rayCB->setChecked)
//
for (const auto &gid : ConnectionManager->AllGroups())
{
groupCombo->addItem(GetDisplayName(gid), gid.toString());
}
} }
void ImportConfigWindow::UpdateColorScheme() void ImportConfigWindow::UpdateColorScheme()
@ -54,23 +64,28 @@ QMultiHash<QString, CONFIGROOT> ImportConfigWindow::SelectConnection(bool outbou
// false and disable the checkbox // false and disable the checkbox
keepImportedInboundCheckBox->setEnabled(!outboundsOnly); keepImportedInboundCheckBox->setEnabled(!outboundsOnly);
routeEditBtn->setEnabled(!outboundsOnly); routeEditBtn->setEnabled(!outboundsOnly);
groupCombo->setEnabled(false);
this->exec(); this->exec();
QMultiHash<QString, CONFIGROOT> conn; QMultiHash<QString, CONFIGROOT> conn;
for (const auto &connEntry : connections.values()) for (const auto &connEntry : connectionsToNewGroup.values())
{
conn += connEntry;
}
for (const auto &connEntry : connectionsToExistingGroup.values())
{ {
conn += connEntry; conn += connEntry;
} }
return result() == Accepted ? conn : QMultiHash<QString, CONFIGROOT>{}; return result() == Accepted ? conn : QMultiHash<QString, CONFIGROOT>{};
} }
int ImportConfigWindow::ImportConnection() int ImportConfigWindow::PerformImportConnection()
{ {
this->exec(); this->exec();
int count = 0; int count = 0;
for (const auto &groupName : connections.keys()) for (const auto &groupObject : connectionsToNewGroup)
{ {
GroupId groupId = groupName.isEmpty() ? DefaultGroupId : ConnectionManager->CreateGroup(groupName, false); const auto groupName = connectionsToNewGroup.key(groupObject);
const auto groupObject = connections[groupName]; GroupId groupId = ConnectionManager->CreateGroup(groupName, false);
for (const auto &connConf : groupObject) for (const auto &connConf : groupObject)
{ {
auto connName = groupObject.key(connConf); auto connName = groupObject.key(connConf);
@ -84,6 +99,21 @@ int ImportConfigWindow::ImportConnection()
} }
} }
for (const auto &groupObject : connectionsToExistingGroup)
{
const auto groupId = connectionsToExistingGroup.key(groupObject);
for (const auto &connConf : groupObject)
{
auto connName = groupObject.key(connConf);
auto [protocol, host, port] = GetConnectionInfo(connConf);
if (connName.isEmpty())
{
connName = protocol + "/" + host + ":" + QSTRN(port) + "-" + GenerateRandomString(5);
}
ConnectionManager->CreateConnection(connConf, connName, groupId, true);
}
}
return count; return count;
} }
@ -116,7 +146,7 @@ void ImportConfigWindow::on_qrFromScreenBtn_clicked()
if (_r == QDialog::Accepted) if (_r == QDialog::Accepted)
{ {
auto str = DecodeQRCode(pix); auto str = DecodeQRCode(pix);
qrImageLabel->setPixmap(QPixmap::fromImage(pix));
if (str.trimmed().isEmpty()) if (str.trimmed().isEmpty())
{ {
LOG(MODULE_UI, "Cannot decode QR Code from an image, size: h=" + QSTRN(pix.width()) + ", v=" + QSTRN(pix.height())) LOG(MODULE_UI, "Cannot decode QR Code from an image, size: h=" + QSTRN(pix.width()) + ", v=" + QSTRN(pix.height()))
@ -124,7 +154,7 @@ void ImportConfigWindow::on_qrFromScreenBtn_clicked()
} }
else else
{ {
vmessConnectionStringTxt->appendPlainText(str.trimmed() + NEWLINE); qrCodeLinkTxt->setText(str.trimmed());
} }
} }
} }
@ -135,7 +165,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked()
switch (tabWidget->currentIndex()) switch (tabWidget->currentIndex())
{ {
case 0: case LINK_PAGE:
{ {
QStringList linkList = SplitLines(vmessConnectionStringTxt->toPlainText()); QStringList linkList = SplitLines(vmessConnectionStringTxt->toPlainText());
// //
@ -165,11 +195,19 @@ void ImportConfigWindow::on_beginImportBtn_clicked()
linkErrors[link] = QSTRN(linkErrors.count() + 1) + ": " + errMessage; linkErrors[link] = QSTRN(linkErrors.count() + 1) + ": " + errMessage;
continue; continue;
} }
else else if (newGroupName.isEmpty())
{ {
for (const auto &conf : config) for (const auto &conf : config)
{ {
AddToGroup(newGroupName, config.key(conf), conf); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(config.key(conf), conf);
}
}
else
{
for (const auto &conf : config)
{
connectionsToNewGroup[newGroupName].insert(config.key(conf), conf);
} }
} }
} }
@ -189,7 +227,26 @@ void ImportConfigWindow::on_beginImportBtn_clicked()
break; break;
} }
case 2: case QRCODE_PAGE:
{
QString errorMsg;
const auto root = ConvertConfigFromString(qrCodeLinkTxt->text(), &aliasPrefix, &errorMsg);
if (!errorMsg.isEmpty())
{
QvMessageBoxWarn(this, tr("Failed to import connection"), errorMsg);
break;
}
for (const auto &conf : root)
{
connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(root.key(conf), conf);
}
break;
}
case MANUAL_PAGE:
{
break;
}
case ADVANCED_PAGE:
{ {
// From File... // From File...
bool ImportAsComplex = keepImportedInboundCheckBox->isChecked(); bool ImportAsComplex = keepImportedInboundCheckBox->isChecked();
@ -203,7 +260,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked()
aliasPrefix += "_" + QFileInfo(path).fileName(); aliasPrefix += "_" + QFileInfo(path).fileName();
CONFIGROOT config = ConvertConfigFromFile(path, ImportAsComplex); CONFIGROOT config = ConvertConfigFromFile(path, ImportAsComplex);
AddToGroup("", aliasPrefix, config); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(aliasPrefix, config);
break; break;
} }
} }
@ -275,7 +332,7 @@ void ImportConfigWindow::on_connectionEditBtn_clicked()
CONFIGROOT root; CONFIGROOT root;
root.insert("outbounds", outboundsList); root.insert("outbounds", outboundsList);
// //
AddToGroup("", alias, root); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, root);
accept(); accept();
} }
} }
@ -291,12 +348,13 @@ void ImportConfigWindow::on_subscriptionButton_clicked()
GroupManager w(this); GroupManager w(this);
w.exec(); w.exec();
auto importToComplex = !keepImportedInboundCheckBox->isEnabled(); auto importToComplex = !keepImportedInboundCheckBox->isEnabled();
connections.clear(); connectionsToNewGroup.clear();
connectionsToExistingGroup.clear();
if (importToComplex) if (importToComplex)
{ {
auto [alias, conf] = w.GetSelectedConfig(); auto [alias, conf] = w.GetSelectedConfig();
AddToGroup("", alias, conf); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, conf);
} }
accept(); accept();
@ -311,7 +369,7 @@ void ImportConfigWindow::on_routeEditBtn_clicked()
if (isChanged) if (isChanged)
{ {
AddToGroup("", alias, result); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, result);
accept(); accept();
} }
} }
@ -331,7 +389,7 @@ void ImportConfigWindow::on_jsonEditBtn_clicked()
if (isChanged) if (isChanged)
{ {
AddToGroup("", alias, CONFIGROOT(result)); connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, CONFIGROOT(result));
accept(); accept();
} }
} }

View File

@ -15,7 +15,7 @@ class ImportConfigWindow
public: public:
explicit ImportConfigWindow(QWidget *parent = nullptr); explicit ImportConfigWindow(QWidget *parent = nullptr);
~ImportConfigWindow(); ~ImportConfigWindow();
int ImportConnection(); int PerformImportConnection();
QMultiHash<QString, CONFIGROOT> SelectConnection(bool outboundsOnly); QMultiHash<QString, CONFIGROOT> SelectConnection(bool outboundsOnly);
private: private:
@ -43,19 +43,8 @@ class ImportConfigWindow
private: private:
void UpdateColorScheme(); void UpdateColorScheme();
QMap<QString, QMultiHash<QString, CONFIGROOT>> connections;
QMap<QString, QString> linkErrors; QMap<QString, QString> linkErrors;
void AddToGroup(const QString &groupName, const QString &alias, const CONFIGROOT &root) //
{ QHash<GroupId, QMultiHash<QString, CONFIGROOT>> connectionsToExistingGroup;
if (connections.contains(groupName)) QHash<QString, QMultiHash<QString, CONFIGROOT>> connectionsToNewGroup;
{
connections[groupName].insert(alias, root);
}
else
{
QMultiHash<QString, CONFIGROOT> temp;
temp.insert(alias, root);
connections.insert(groupName, temp);
}
}
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>522</width> <width>549</width>
<height>460</height> <height>482</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
@ -42,15 +42,18 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="1" column="0">
</item> <widget class="QLabel" name="label_9">
<item>
<widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Import Source</string> <string>Import To Group</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<widget class="QComboBox" name="groupCombo"/>
</item>
</layout>
</item>
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
@ -58,18 +61,23 @@
</property> </property>
<widget class="QWidget" name="tabWidgetPage2"> <widget class="QWidget" name="tabWidgetPage2">
<attribute name="title"> <attribute name="title">
<string>VMess / QRCode</string> <string>Link</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_8">
<property name="text"> <property name="text">
<string>Connection Share Link</string> <string>Share Link</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Error List</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0"> <item row="1" column="0">
<widget class="QPlainTextEdit" name="vmessConnectionStringTxt"> <widget class="QPlainTextEdit" name="vmessConnectionStringTxt">
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
@ -86,23 +94,12 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QListWidget" name="errorsList"/> <widget class="QListWidget" name="errorsList"/>
</item> </item>
<item row="0" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Error List</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Share Link</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </widget>
<item> <widget class="QWidget" name="tab">
<attribute name="title">
<string>QR Code</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3"> <layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
@ -141,6 +138,13 @@
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6"> <layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="hideQv2rayCB">
<property name="text">
<string>Hide Qv2ray</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QDoubleSpinBox" name="doubleSpinBox"> <widget class="QDoubleSpinBox" name="doubleSpinBox">
<property name="suffix"> <property name="suffix">
@ -164,13 +168,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="hideQv2rayCB">
<property name="text">
<string>Hide Qv2ray</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">
@ -199,56 +196,58 @@
</item> </item>
</layout> </layout>
</item> </item>
</layout> <item row="2" column="0">
</item> <widget class="QLabel" name="label_10">
</layout>
</widget>
<widget class="QWidget" name="tabWidgetPage4">
<attribute name="title">
<string>Subscriptions / Manually Input</string>
</attribute>
<layout class="QFormLayout" name="formLayout_4">
<item row="1" column="1">
<widget class="QPushButton" name="subscriptionButton">
<property name="text"> <property name="text">
<string>Open Subscription Manager</string> <string>Image</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="2" column="1">
<widget class="Line" name="line"> <widget class="QLabel" name="qrImageLabel">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>10</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_16">
<property name="text"> <property name="text">
<string>Manually Input Connections</string> <string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Detected Link</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="qrCodeLinkTxt">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Input Manually</string>
</attribute>
<layout class="QFormLayout" name="formLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="label_12"> <widget class="QLabel" name="label_12">
<property name="text"> <property name="text">
<string>Connection Editor</string> <string>Simple Editor</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="connectionEditBtn"> <widget class="QPushButton" name="connectionEditBtn">
<property name="text"> <property name="text">
<string>Open Connection Editor</string> <string>Open Connection Editor</string>
@ -258,51 +257,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_15"> <widget class="QLabel" name="label_15">
<property name="text"> <property name="text">
<string>Route Editor</string> <string>Complex Editor</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="2" column="1">
<widget class="QPushButton" name="routeEditBtn"> <widget class="QPushButton" name="routeEditBtn">
<property name="text"> <property name="text">
<string>Open Route Editor</string> <string>Open Route / Complex Connection Editor</string>
</property> </property>
<property name="autoDefault"> <property name="autoDefault">
<bool>false</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Json Editor</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="jsonEditBtn">
<property name="text">
<string>Open JSON Editor</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Subscription Manager</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_14"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Subscription Link</string> <string>You can manually input connection here.</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -310,17 +285,24 @@
</widget> </widget>
<widget class="QWidget" name="tabWidgetPage1"> <widget class="QWidget" name="tabWidgetPage1">
<attribute name="title"> <attribute name="title">
<string>Existing File</string> <string>Advanced</string>
</attribute> </attribute>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="0" column="0"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Manually Input Connections</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="fileLabel"> <widget class="QLabel" name="fileLabel">
<property name="text"> <property name="text">
<string>Path</string> <string>Path</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QLineEdit" name="fileLineTxt"> <widget class="QLineEdit" name="fileLineTxt">
@ -338,13 +320,30 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="keepImportedInboundCheckBox"> <widget class="QCheckBox" name="keepImportedInboundCheckBox">
<property name="text"> <property name="text">
<string>Import as Complex Config (Manually edit route rules and inbounds)</string> <string>Import as Complex Config (Manually edit route rules and inbounds)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Json Editor</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="jsonEditBtn">
<property name="text">
<string>Open JSON Editor</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -384,21 +383,10 @@
</widget> </widget>
<tabstops> <tabstops>
<tabstop>nameTxt</tabstop> <tabstop>nameTxt</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>vmessConnectionStringTxt</tabstop> <tabstop>vmessConnectionStringTxt</tabstop>
<tabstop>errorsList</tabstop> <tabstop>errorsList</tabstop>
<tabstop>imageFileEdit</tabstop>
<tabstop>selectImageBtn</tabstop>
<tabstop>doubleSpinBox</tabstop>
<tabstop>hideQv2rayCB</tabstop>
<tabstop>qrFromScreenBtn</tabstop>
<tabstop>subscriptionButton</tabstop>
<tabstop>connectionEditBtn</tabstop>
<tabstop>routeEditBtn</tabstop>
<tabstop>jsonEditBtn</tabstop>
<tabstop>fileLineTxt</tabstop> <tabstop>fileLineTxt</tabstop>
<tabstop>selectFileBtn</tabstop> <tabstop>selectFileBtn</tabstop>
<tabstop>keepImportedInboundCheckBox</tabstop>
<tabstop>beginImportBtn</tabstop> <tabstop>beginImportBtn</tabstop>
<tabstop>cancelImportBtn</tabstop> <tabstop>cancelImportBtn</tabstop>
</tabstops> </tabstops>

View File

@ -576,7 +576,7 @@ void MainWindow::on_action_RCM_DeleteThese_triggered()
void MainWindow::on_importConfigButton_clicked() void MainWindow::on_importConfigButton_clicked()
{ {
ImportConfigWindow w(this); ImportConfigWindow w(this);
w.ImportConnection(); w.PerformImportConnection();
} }
void MainWindow::on_action_RCM_EditAsComplex_triggered() void MainWindow::on_action_RCM_EditAsComplex_triggered()

View File

@ -86,7 +86,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/locate.png</normaloff>:/assets/icons/ui_light/locate.png</iconset> <normaloff>:/assets/icons/ui_light/locate.png</normaloff>:/assets/icons/ui_light/locate.png</iconset>
</property> </property>
</widget> </widget>
@ -110,7 +110,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/sort.png</normaloff>:/assets/icons/ui_light/sort.png</iconset> <normaloff>:/assets/icons/ui_light/sort.png</normaloff>:/assets/icons/ui_light/sort.png</iconset>
</property> </property>
<property name="popupMode"> <property name="popupMode">
@ -291,7 +291,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset> <normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset>
</property> </property>
</widget> </widget>
@ -354,7 +354,7 @@
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../../resources.qrc"> <iconset resource="../../../resources.qrc">
<normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset> <normaloff>:/assets/icons/ui_light/delete.png</normaloff>:/assets/icons/ui_light/delete.png</iconset>
</property> </property>
</widget> </widget>
@ -538,7 +538,7 @@
<tabstop>importConfigButton</tabstop> <tabstop>importConfigButton</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../resources.qrc"/> <include location="../../../resources.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>