refactor group/profile relation

This commit is contained in:
Nova 2025-07-09 04:37:39 +03:30
parent ce6cfe3584
commit b82d937964
13 changed files with 252 additions and 383 deletions

View File

@ -208,6 +208,8 @@ set(PROJECT_SOURCES
src/stats/connectionLister/connectionLister.cpp
src/configs/proxy/Json2Bean.cpp
include/sys/windows/eventHandler.h
src/dataStore/Group.cpp
src/dataStore/ProxyEntity.cpp
)
# Qt exe

View File

@ -38,8 +38,6 @@ namespace NekoGui {
void DeleteProfile(int id);
void MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid);
std::shared_ptr<ProxyEntity> GetProfile(int id);
bool AddGroup(const std::shared_ptr<Group> &ent);
@ -78,6 +76,8 @@ namespace NekoGui {
static std::shared_ptr<Group> LoadGroup(const QString &jsonPath);
static std::shared_ptr<RoutingChain> LoadRouteChain(const QString &jsonPath);
void deleteProfile(int id);
};
extern ProfileManager *profileManager;

View File

@ -1,9 +1,10 @@
#pragma once
#include "include/global/NekoGui.hpp"
#include "ProxyEntity.hpp"
#include "include/global/NekoGui.hpp"
namespace NekoGui {
namespace NekoGui
{
class Group : public JsonStore {
public:
int id = -1;
@ -19,14 +20,20 @@ namespace NekoGui {
// list ui
bool manually_column_width = false;
QList<int> column_width;
QList<int> order;
QList<int> profiles;
Group();
// 按 id 顺序
[[nodiscard]] QList<std::shared_ptr<ProxyEntity>> Profiles() const;
[[nodiscard]] QList<int> Profiles() const;
// 按 显示 顺序
[[nodiscard]] QList<std::shared_ptr<ProxyEntity>> ProfilesWithOrder() const;
[[nodiscard]] QList<std::shared_ptr<ProxyEntity>> GetProfileEnts() const;
bool RemoveProfile(int id);
bool AddProfile(int id);
bool SwapProfiles(int idx1, int idx2);
bool HasProfile(int id) const;
};
} // namespace NekoGui
}// namespace NekoGui

View File

@ -55,7 +55,6 @@ namespace NekoGui {
bool flag_tray = false;
bool flag_debug = false;
bool flag_restart_tun_on = false;
bool flag_reorder = false;
bool flag_dns_set = false;
// Saved

View File

@ -10,64 +10,21 @@
class MyTableWidget : public QTableWidget {
public:
explicit MyTableWidget(QWidget *parent = nullptr) : QTableWidget(parent) {
// 拖拽设置
this->setDragDropMode(QAbstractItemView::InternalMove); // 内部移动
this->setDropIndicatorShown(true); // drop位置 提示
this->setSelectionBehavior(QAbstractItemView::SelectRows);
};
QList<int> order; // id sorted (save)
std::map<int, int> id2Row; // id2Row
QList<int> row2Id; // row2Id: use this to refresh data
std::function<void()> callback_save_order;
std::function<void(int id)> refresh_data;
void _save_order(bool saveToFile) {
order.clear();
id2Row.clear();
for (int i = 0; i < this->rowCount(); i++) {
auto id = row2Id[i];
order += id;
id2Row[id] = i;
}
if (callback_save_order != nullptr && saveToFile)
{
callback_save_order();
}
this->setDragDropMode(InternalMove);
this->setDropIndicatorShown(true);
this->setSelectionBehavior(SelectRows);
}
void update_order(bool saveToFile) {
if (order.isEmpty()) {
_save_order(false);
return;
}
// Then save the order
_save_order(saveToFile);
};
std::function<void(int row1, int row2)> rowsSwapped;
protected:
void dropEvent(QDropEvent *event) override {
if (order.isEmpty()) return;
int row_src, row_dst;
row_src = this->currentRow();
auto id_src = row2Id[row_src];
QTableWidgetItem *item = this->itemAt(event->position().toPoint());
if (item != nullptr) {
row_dst = item->row();
// Modify order
row2Id.swapItemsAt(row_src, row_dst);
order.removeAt(row_src);
order.insert(row_dst, id_src);
} else {
return;
if (const QTableWidgetItem *item = this->itemAt(event->position().toPoint()); item != nullptr) {
const int row_dst = item->row();
if (rowsSwapped)
{
rowsSwapped(row_dst, currentRow());
clearSelection();
}
}
// Do update order & refresh
clearSelection();
update_order(true);
refresh_data(-1);
};
}
};

View File

@ -752,12 +752,10 @@ namespace NekoGui_sub {
QList<std::shared_ptr<NekoGui::ProxyEntity>> update_del; // 更新前后都有的,需要删除的新配置
QList<std::shared_ptr<NekoGui::ProxyEntity>> update_keep; // 更新前后都有的,被保留的旧配置
// 订阅解析前
if (group != nullptr) {
in = group->Profiles();
in = group->GetProfileEnts();
group->sub_last_update = QDateTime::currentMSecsSinceEpoch() / 1000;
group->info = sub_user_info;
group->order.clear();
group->Save();
//
if (NekoGui::dataStore->sub_clear) {
@ -771,7 +769,7 @@ namespace NekoGui_sub {
rawUpdater->update(content);
if (group != nullptr) {
out_all = group->Profiles();
out_all = group->GetProfileEnts();
QString change_text;
@ -786,7 +784,6 @@ namespace NekoGui_sub {
NekoGui::ProfileFilter::OnlyInSrc(in, out, only_in);
NekoGui::ProfileFilter::OnlyInSrc(out, in, only_out);
NekoGui::ProfileFilter::Common(in, out, update_keep, update_del, false);
QString notice_added;
QString notice_deleted;
if (only_out.size() < 1000)
@ -810,22 +807,22 @@ namespace NekoGui_sub {
// sort according to order in remote
group->order = {};
group->profiles.clear();
for (const auto &ent: rawUpdater->updated_order) {
auto deleted_index = update_del.indexOf(ent);
if (deleted_index >= 0) {
if (deleted_index >= update_keep.count()) continue; // should not happen
auto ent2 = update_keep[deleted_index];
group->order.append(ent2->id);
const auto& ent2 = update_keep[deleted_index];
group->profiles.append(ent2->id);
} else {
group->order.append(ent->id);
group->profiles.append(ent->id);
}
}
group->Save();
// cleanup
for (const auto &ent: out_all) {
if (!group->order.contains(ent->id)) {
if (!group->HasProfile(ent->id)) {
NekoGui::profileManager->DeleteProfile(ent->id);
}
}

View File

@ -2,9 +2,7 @@
#include "include/configs/proxy/includes.h"
#include <QFile>
#include <QDir>
#include <QColor>
namespace NekoGui {
@ -55,7 +53,7 @@ namespace NekoGui {
}
// Clear Corrupted profile
for (auto id: delProfile) {
DeleteProfile(id);
deleteProfile(id);
}
// Load Groups
auto loadedOrder = groupsTabOrder;
@ -91,71 +89,16 @@ namespace NekoGui {
// First setup
if (groups.empty()) {
auto defaultGroup = NekoGui::ProfileManager::NewGroup();
auto defaultGroup = NewGroup();
defaultGroup->name = QObject::tr("Default");
NekoGui::profileManager->AddGroup(defaultGroup);
profileManager->AddGroup(defaultGroup);
}
if (routes.empty()) {
auto defaultRoute = RoutingChain::GetDefaultChain();
profileManager->AddRouteChain(defaultRoute);
}
// Add default route chains
routes[IranBypassChainID] = RoutingChain::GetIranDefaultChain();
routes[ChinaBypassChainID] = RoutingChain::GetChinaDefaultChain();
//
if (dataStore->flag_reorder) {
{
// remove all (contains orphan)
for (const auto &profile: profiles) {
QFile::remove(profile.second->fn);
}
}
std::map<int, int> gidOld2New;
{
int i = 0;
int ii = 0;
QList<int> newProfilesIdOrder;
std::map<int, std::shared_ptr<ProxyEntity>> newProfiles;
for (auto gid: groupsTabOrder) {
auto group = GetGroup(gid);
gidOld2New[gid] = ii++;
for (auto const &profile: group->ProfilesWithOrder()) {
auto oldId = profile->id;
auto newId = i++;
profile->id = newId;
profile->gid = gidOld2New[gid];
profile->fn = QString("profiles/%1.json").arg(newId);
profile->Save();
newProfiles[newId] = profile;
newProfilesIdOrder << newId;
}
group->order = {};
group->Save();
}
profiles = newProfiles;
profilesIdOrder = newProfilesIdOrder;
}
{
QList<int> newGroupsIdOrder;
std::map<int, std::shared_ptr<Group>> newGroups;
for (auto oldGid: groupsTabOrder) {
auto newId = gidOld2New[oldGid];
auto group = groups[oldGid];
QFile::remove(group->fn);
group->id = newId;
group->fn = QString("groups/%1.json").arg(newId);
group->Save();
newGroups[newId] = group;
newGroupsIdOrder << newId;
}
groups = newGroups;
groupsIdOrder = newGroupsIdOrder;
groupsTabOrder = newGroupsIdOrder;
}
MessageBoxInfo(software_name, "Profiles and groups reorder complete.");
routes[IranBypassChainID] = RoutingChain::GetIranDefaultChain();
routes[ChinaBypassChainID] = RoutingChain::GetChinaDefaultChain();
}
}
@ -243,54 +186,6 @@ namespace NekoGui {
return ent;
}
// ProxyEntity
ProxyEntity::ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_) {
if (type_ != nullptr) this->type = type_;
_add(new configItem("type", &type, itemType::string));
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("gid", &gid, itemType::integer));
_add(new configItem("yc", &latency, itemType::integer));
_add(new configItem("dl", &dl_speed, itemType::string));
_add(new configItem("ul", &ul_speed, itemType::string));
_add(new configItem("report", &full_test_report, itemType::string));
// 可以不关联 bean只加载 ProxyEntity 的信息
if (bean != nullptr) {
this->bean = std::shared_ptr<NekoGui_fmt::AbstractBean>(bean);
// 有虚函数就要在这里 dynamic_cast
_add(new configItem("bean", dynamic_cast<JsonStore *>(bean), itemType::jsonStore));
_add(new configItem("traffic", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));
}
};
QString ProxyEntity::DisplayTestResult() const {
QString result;
if (latency < 0) {
result = "Unavailable";
} else if (latency > 0) {
result = UNICODE_LRO + QString("%1 ms").arg(latency);
}
if (!dl_speed.isEmpty()) result += "" + dl_speed;
if (!ul_speed.isEmpty()) result += "" + ul_speed;
return result;
}
QColor ProxyEntity::DisplayLatencyColor() const {
if (latency < 0) {
return Qt::red;
} else if (latency > 0) {
if (latency <= 200) {
return Qt::darkGreen;
} else {
return Qt::darkYellow;
}
} else {
return {};
}
}
// Profile
int ProfileManager::NewProfileID() const {
@ -306,8 +201,16 @@ namespace NekoGui {
return false;
}
ent->gid = gid < 0 ? dataStore->current_group : gid;
ent->id = NewProfileID();
ent->gid = gid < 0 ? dataStore->current_group : gid;
if (auto group = GetGroup(ent->gid); group != nullptr)
{
group->AddProfile(ent->id);
group->Save();
} else
{
return false;
}
profiles[ent->id] = ent;
profilesIdOrder.push_back(ent->id);
@ -319,26 +222,23 @@ namespace NekoGui {
void ProfileManager::DeleteProfile(int id) {
if (id < 0) return;
if (dataStore->started_id == id) return;
auto ent = GetProfile(id);
if (ent == nullptr) return;
if (auto group = GetGroup(ent->gid); group != nullptr)
{
group->RemoveProfile(id);
group->Save();
}
deleteProfile(id);
}
void ProfileManager::deleteProfile(int id)
{
profiles.erase(id);
profilesIdOrder.removeAll(id);
QFile(QString("profiles/%1.json").arg(id)).remove();
}
void ProfileManager::MoveProfile(const std::shared_ptr<ProxyEntity> &ent, int gid) {
if (gid == ent->gid || gid < 0) return;
auto oldGroup = GetGroup(ent->gid);
if (oldGroup != nullptr && !oldGroup->order.isEmpty()) {
oldGroup->order.removeAll(ent->id);
oldGroup->Save();
}
auto newGroup = GetGroup(gid);
if (newGroup != nullptr && !newGroup->order.isEmpty()) {
newGroup->order.push_back(ent->id);
newGroup->Save();
}
ent->gid = gid;
ent->Save();
}
std::shared_ptr<ProxyEntity> ProfileManager::GetProfile(int id) {
return profiles.count(id) ? profiles[id] : nullptr;
@ -359,21 +259,6 @@ namespace NekoGui {
}
// Group
Group::Group() {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("front_proxy_id", &front_proxy_id, itemType::integer));
_add(new configItem("landing_proxy_id", &landing_proxy_id, itemType::integer));
_add(new configItem("archive", &archive, itemType::boolean));
_add(new configItem("skip_auto_update", &skip_auto_update, itemType::boolean));
_add(new configItem("name", &name, itemType::string));
_add(new configItem("order", &order, itemType::integerList));
_add(new configItem("url", &url, itemType::string));
_add(new configItem("info", &info, itemType::string));
_add(new configItem("lastup", &sub_last_update, itemType::integer64));
_add(new configItem("manually_column_width", &manually_column_width, itemType::boolean));
_add(new configItem("column_width", &column_width, itemType::integerList));
}
std::shared_ptr<Group> ProfileManager::LoadGroup(const QString &jsonPath) {
auto ent = std::make_shared<Group>();
ent->fn = jsonPath;
@ -406,12 +291,10 @@ namespace NekoGui {
void ProfileManager::DeleteGroup(int gid) {
if (groups.size() <= 1) return;
QList<int> toDelete;
for (const auto &[id, profile]: profiles) {
if (profile->gid == gid) toDelete += id; // map访问中不能操作
}
for (const auto &id: toDelete) {
DeleteProfile(id);
auto group = GetGroup(gid);
if (group == nullptr) return;
for (const auto id : group->Profiles()) {
deleteProfile(id);
}
groups.erase(gid);
groupsIdOrder.removeAll(gid);
@ -427,48 +310,6 @@ namespace NekoGui {
return GetGroup(dataStore->current_group);
}
RouteRule::RouteRule() {
_add(new configItem("name", &name, itemType::string));
_add(new configItem("ip_version", &ip_version, itemType::string));
_add(new configItem("network", &network, itemType::string));
_add(new configItem("protocol", &protocol, itemType::string));
_add(new configItem("inbound", &inbound, itemType::stringList));
_add(new configItem("domain", &domain, itemType::stringList));
_add(new configItem("domain_suffix", &domain_suffix, itemType::stringList));
_add(new configItem("domain_keyword", &domain_keyword, itemType::stringList));
_add(new configItem("domain_regex", &domain_regex, itemType::stringList));
_add(new configItem("source_ip_cidr", &source_ip_cidr, itemType::stringList));
_add(new configItem("source_ip_is_private", &source_ip_is_private, itemType::boolean));
_add(new configItem("ip_cidr", &ip_cidr, itemType::stringList));
_add(new configItem("ip_is_private", &ip_is_private, itemType::boolean));
_add(new configItem("source_port", &source_port, itemType::stringList));
_add(new configItem("source_port_range", &source_port_range, itemType::stringList));
_add(new configItem("port", &port, itemType::stringList));
_add(new configItem("port_range", &port_range, itemType::stringList));
_add(new configItem("process_name", &process_name, itemType::stringList));
_add(new configItem("process_path", &process_path, itemType::stringList));
_add(new configItem("process_path_regex", &process_path_regex, itemType::stringList));
_add(new configItem("rule_set", &rule_set, itemType::stringList));
_add(new configItem("invert", &invert, itemType::boolean));
_add(new configItem("outboundID", &outboundID, itemType::integer));
_add(new configItem("actionType", &action, itemType::string));
_add(new configItem("rejectMethod", &rejectMethod, itemType::string));
_add(new configItem("noDrop", &no_drop, itemType::boolean));
_add(new configItem("override_address", &override_address, itemType::string));
_add(new configItem("override_port", &override_port, itemType::integer));
_add(new configItem("sniffers", &sniffers, itemType::stringList));
_add(new configItem("sniffOverrideDest", &sniffOverrideDest, itemType::boolean));
_add(new configItem("strategy", &strategy, itemType::string));
_add(new configItem("type", &type, itemType::integer));
_add(new configItem("simple_action", &simpleAction, itemType::integer));
}
RoutingChain::RoutingChain() {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("name", &name, itemType::string));
_add(new configItem("rules", &castedRules, itemType::jsonStoreList));
}
std::shared_ptr<RoutingChain> ProfileManager::NewRouteChain() {
auto route = std::make_shared<RoutingChain>();
return route;
@ -518,25 +359,4 @@ namespace NekoGui {
}
}
QList<std::shared_ptr<ProxyEntity>> Group::Profiles() const {
QList<std::shared_ptr<ProxyEntity>> ret;
for (const auto &[_, profile]: profileManager->profiles) {
if (id == profile->gid) ret += profile;
}
return ret;
}
QList<std::shared_ptr<ProxyEntity>> Group::ProfilesWithOrder() const {
if (order.isEmpty()) {
return Profiles();
} else {
QList<std::shared_ptr<ProxyEntity>> ret;
for (auto _id: order) {
auto ent = profileManager->GetProfile(_id);
if (ent != nullptr) ret += ent;
}
return ret;
}
}
} // namespace NekoGui

65
src/dataStore/Group.cpp Normal file
View File

@ -0,0 +1,65 @@
#include <include/dataStore/Group.hpp>
#include "include/ui/profile/dialog_edit_profile.h"
namespace NekoGui
{
Group::Group() {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("front_proxy_id", &front_proxy_id, itemType::integer));
_add(new configItem("landing_proxy_id", &landing_proxy_id, itemType::integer));
_add(new configItem("archive", &archive, itemType::boolean));
_add(new configItem("skip_auto_update", &skip_auto_update, itemType::boolean));
_add(new configItem("name", &name, itemType::string));
_add(new configItem("profiles", &profiles, itemType::integerList));
_add(new configItem("url", &url, itemType::string));
_add(new configItem("info", &info, itemType::string));
_add(new configItem("lastup", &sub_last_update, itemType::integer64));
_add(new configItem("manually_column_width", &manually_column_width, itemType::boolean));
_add(new configItem("column_width", &column_width, itemType::integerList));
}
QList<int> Group::Profiles() const {
return profiles;
}
QList<std::shared_ptr<ProxyEntity>> Group::GetProfileEnts() const
{
auto res = QList<std::shared_ptr<ProxyEntity>>{};
for (auto id : profiles)
{
res.append(profileManager->GetProfile(id));
}
return res;
}
bool Group::AddProfile(int id)
{
if (HasProfile(id))
{
return false;
}
profiles.append(id);
return true;
}
bool Group::RemoveProfile(int id)
{
if (!HasProfile(id)) return false;
profiles.removeAll(id);
return true;
}
bool Group::SwapProfiles(int idx1, int idx2)
{
if (profiles.size() <= idx1 || profiles.size() <= idx2) return false;
profiles.swapItemsAt(idx1, idx2);
return true;
}
bool Group::HasProfile(int id) const
{
return profiles.contains(id);
}
}

View File

@ -0,0 +1,48 @@
#include <include/dataStore/ProxyEntity.hpp>
namespace NekoGui
{
ProxyEntity::ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_) {
if (type_ != nullptr) this->type = type_;
_add(new configItem("type", &type, itemType::string));
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("gid", &gid, itemType::integer));
_add(new configItem("yc", &latency, itemType::integer));
_add(new configItem("dl", &dl_speed, itemType::string));
_add(new configItem("ul", &ul_speed, itemType::string));
_add(new configItem("report", &full_test_report, itemType::string));
if (bean != nullptr) {
this->bean = std::shared_ptr<NekoGui_fmt::AbstractBean>(bean);
_add(new configItem("bean", dynamic_cast<JsonStore *>(bean), itemType::jsonStore));
_add(new configItem("traffic", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));
}
};
QString ProxyEntity::DisplayTestResult() const {
QString result;
if (latency < 0) {
result = "Unavailable";
} else if (latency > 0) {
result = UNICODE_LRO + QString("%1 ms").arg(latency);
}
if (!dl_speed.isEmpty()) result += "" + dl_speed;
if (!ul_speed.isEmpty()) result += "" + ul_speed;
return result;
}
QColor ProxyEntity::DisplayLatencyColor() const {
if (latency < 0) {
return Qt::red;
} else if (latency > 0) {
if (latency <= 200) {
return Qt::darkGreen;
} else {
return Qt::darkYellow;
}
} else {
return {};
}
}
}

View File

@ -22,6 +22,42 @@ namespace NekoGui {
return false;
}
RouteRule::RouteRule() {
_add(new configItem("name", &name, itemType::string));
_add(new configItem("ip_version", &ip_version, itemType::string));
_add(new configItem("network", &network, itemType::string));
_add(new configItem("protocol", &protocol, itemType::string));
_add(new configItem("inbound", &inbound, itemType::stringList));
_add(new configItem("domain", &domain, itemType::stringList));
_add(new configItem("domain_suffix", &domain_suffix, itemType::stringList));
_add(new configItem("domain_keyword", &domain_keyword, itemType::stringList));
_add(new configItem("domain_regex", &domain_regex, itemType::stringList));
_add(new configItem("source_ip_cidr", &source_ip_cidr, itemType::stringList));
_add(new configItem("source_ip_is_private", &source_ip_is_private, itemType::boolean));
_add(new configItem("ip_cidr", &ip_cidr, itemType::stringList));
_add(new configItem("ip_is_private", &ip_is_private, itemType::boolean));
_add(new configItem("source_port", &source_port, itemType::stringList));
_add(new configItem("source_port_range", &source_port_range, itemType::stringList));
_add(new configItem("port", &port, itemType::stringList));
_add(new configItem("port_range", &port_range, itemType::stringList));
_add(new configItem("process_name", &process_name, itemType::stringList));
_add(new configItem("process_path", &process_path, itemType::stringList));
_add(new configItem("process_path_regex", &process_path_regex, itemType::stringList));
_add(new configItem("rule_set", &rule_set, itemType::stringList));
_add(new configItem("invert", &invert, itemType::boolean));
_add(new configItem("outboundID", &outboundID, itemType::integer));
_add(new configItem("actionType", &action, itemType::string));
_add(new configItem("rejectMethod", &rejectMethod, itemType::string));
_add(new configItem("noDrop", &no_drop, itemType::boolean));
_add(new configItem("override_address", &override_address, itemType::string));
_add(new configItem("override_port", &override_port, itemType::integer));
_add(new configItem("sniffers", &sniffers, itemType::stringList));
_add(new configItem("sniffOverrideDest", &sniffOverrideDest, itemType::boolean));
_add(new configItem("strategy", &strategy, itemType::string));
_add(new configItem("type", &type, itemType::integer));
_add(new configItem("simple_action", &simpleAction, itemType::integer));
}
RouteRule::RouteRule(const RouteRule& other) {
name = other.name;
ip_version = other.ip_version;
@ -805,6 +841,12 @@ namespace NekoGui {
}
}
RoutingChain::RoutingChain() {
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("name", &name, itemType::string));
_add(new configItem("rules", &castedRules, itemType::jsonStoreList));
}
RoutingChain::RoutingChain(const RoutingChain& other) : JsonStore(other) {
id = other.id;
name = QString(other.name);

View File

@ -87,7 +87,6 @@ int main(int argc, char* argv[]) {
if (NekoGui::dataStore->argv.contains("-debug")) NekoGui::dataStore->flag_debug = true;
if (NekoGui::dataStore->argv.contains("-flag_restart_tun_on")) NekoGui::dataStore->flag_restart_tun_on = true;
if (NekoGui::dataStore->argv.contains("-flag_restart_dns_set")) NekoGui::dataStore->flag_dns_set = true;
if (NekoGui::dataStore->argv.contains("-flag_reorder")) NekoGui::dataStore->flag_reorder = true;
#ifdef NKR_CPP_USE_APPDATA
NekoGui::dataStore->flag_use_appdata = true; // Example: Package & MacOS
#endif

View File

@ -261,12 +261,13 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->graph_tab->layout()->addWidget(speedChartWidget);
// table UI
ui->proxyListTable->callback_save_order = [=] {
ui->proxyListTable->rowsSwapped = [=](int row1, int row2)
{
auto group = NekoGui::profileManager->CurrentGroup();
group->order = ui->proxyListTable->order;
group->Save();
group->SwapProfiles(row1, row2);
refresh_proxy_list(group->profiles[row1]);
refresh_proxy_list(group->profiles[row2]);
};
ui->proxyListTable->refresh_data = [=](int id) { refresh_proxy_list_impl_refresh_data(id); };
if (auto button = ui->proxyListTable->findChild<QAbstractButton *>(QString(), Qt::FindDirectChildrenOnly)) {
// Corner Button
connect(button, &QAbstractButton::clicked, this, [=] { refresh_proxy_list_impl(-1, {GroupSortMethod::ById}); });
@ -446,7 +447,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
urltest_current_group(get_now_selected_list());
});
connect(ui->actionUrl_Test_Group, &QAction::triggered, this, [=]() {
urltest_current_group(NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder());
urltest_current_group(NekoGui::profileManager->CurrentGroup()->GetProfileEnts());
});
connect(ui->actionSpeedtest_Current, &QAction::triggered, this, [=]()
{
@ -461,7 +462,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
});
connect(ui->actionSpeedtest_Group, &QAction::triggered, this, [=]()
{
speedtest_current_group(NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder());
speedtest_current_group(NekoGui::profileManager->CurrentGroup()->GetProfileEnts());
});
connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { stopTests(); });
//
@ -805,7 +806,6 @@ void MainWindow::on_menu_exit_triggered() {
arguments.removeFirst();
arguments.removeAll("-tray");
arguments.removeAll("-flag_restart_tun_on");
arguments.removeAll("-flag_reorder");
arguments.removeAll("-flag_restart_dns_set");
}
auto program = QApplication::applicationFilePath();
@ -1267,73 +1267,24 @@ void MainWindow::refresh_proxy_list(const int &id) {
void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSortAction) {
ui->proxyListTable->setUpdatesEnabled(false);
if (id < 0) {
ui->proxyListTable->row2Id.clear();
ui->proxyListTable->setRowCount(0);
auto oldOrder = QList<int>();
oldOrder << ui->proxyListTable->order;
auto group = NekoGui::profileManager->CurrentGroup();
if (group == nullptr)
auto currentGroup = NekoGui::profileManager->CurrentGroup();
if (currentGroup == nullptr)
{
ui->proxyListTable->setUpdatesEnabled(true);
MW_show_log("Could not find current group!");
return;
}
QSet<int> currProfs;
// remove old ones
ui->proxyListTable->order.clear();
for (const int oldID : oldOrder)
{
if (NekoGui::profileManager->GetProfile(oldID) != nullptr)
{
ui->proxyListTable->order << oldID;
currProfs.insert(oldID);
}
}
// add new ones
for (const auto& profile : NekoGui::profileManager->profiles)
{
if (profile.second->gid == group->id && !currProfs.contains(profile.first))
{
ui->proxyListTable->order << profile.first;
}
}
switch (groupSortAction.method) {
case GroupSortMethod::Raw: {
QList<int> newGroupOrder;
QSet<int> newGroupIds;
for (const int oldId : group->order)
{
if (NekoGui::profileManager->GetProfile(oldId) != nullptr)
{
newGroupOrder << oldId;
newGroupIds.insert(oldId);
}
}
for (const auto& profile : NekoGui::profileManager->profiles)
{
if (profile.second->gid == group->id && !newGroupIds.contains(profile.first))
{
newGroupOrder << profile.first;
}
}
group->order.clear();
group->order << newGroupOrder;
ui->proxyListTable->order = group->order;
break;
}
case GroupSortMethod::ById: {
// Clear Order
ui->proxyListTable->order.clear();
ui->proxyListTable->callback_save_order();
break;
}
case GroupSortMethod::ByAddress:
case GroupSortMethod::ByName:
case GroupSortMethod::ByLatency:
case GroupSortMethod::ByType: {
std::sort(ui->proxyListTable->order.begin(), ui->proxyListTable->order.end(),
std::sort(currentGroup->profiles.begin(), currentGroup->profiles.end(),
[=](int a, int b) {
QString ms_a;
QString ms_b;
@ -1380,26 +1331,7 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor
}
}
if (ui->proxyListTable->order.isEmpty())
{
for (const auto& ent : group->Profiles())
{
ui->proxyListTable->order << ent->id;
}
}
bool needSave = oldOrder.size() != ui->proxyListTable->order.size();
for (int i=0;i<oldOrder.size() && !needSave;i++)
{
if (oldOrder[i] != ui->proxyListTable->order[i])
{
needSave = true;
}
}
ui->proxyListTable->row2Id << ui->proxyListTable->order;
ui->proxyListTable->setRowCount(ui->proxyListTable->order.size());
ui->proxyListTable->update_order(needSave);
ui->proxyListTable->setRowCount(currentGroup->profiles.count());
}
// refresh data
@ -1408,24 +1340,24 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor
void MainWindow::refresh_proxy_list_impl_refresh_data(const int &id, bool stopping) {
ui->proxyListTable->setUpdatesEnabled(false);
auto currentGroup = NekoGui::profileManager->CurrentGroup();
if (id >= 0)
{
if (ui->proxyListTable->id2Row.count(id) == 0)
if (!currentGroup->HasProfile(id))
{
qDebug("Invalid proxy list id, data might be corrupted");
ui->proxyListTable->setUpdatesEnabled(true);
return;
}
auto rowID = ui->proxyListTable->id2Row[id];
auto rowID = currentGroup->profiles.indexOf(id);
auto profile = NekoGui::profileManager->GetProfile(id);
refresh_table_item(rowID, profile, stopping);
} else
{
ui->proxyListTable->blockSignals(true);
for (int row = 0; row < ui->proxyListTable->rowCount(); row++) {
auto profileId = ui->proxyListTable->row2Id[row];
int row = 0;
for (const auto profileId : currentGroup->profiles) {
auto profile = NekoGui::profileManager->GetProfile(profileId);
refresh_table_item(row, profile, stopping);
refresh_table_item(row++, profile, stopping);
}
ui->proxyListTable->blockSignals(false);
}
@ -1523,8 +1455,8 @@ void MainWindow::on_menu_delete_repeat_triggered () {
QList<std::shared_ptr<NekoGui::ProxyEntity>> out;
QList<std::shared_ptr<NekoGui::ProxyEntity>> out_del;
NekoGui::ProfileFilter::Uniq (NekoGui::profileManager-> CurrentGroup ()-> Profiles (), out, true , false );
NekoGui::ProfileFilter::OnlyInSrc_ByPointer (NekoGui::profileManager-> CurrentGroup ()-> Profiles (), out, out_del);
NekoGui::ProfileFilter::Uniq (NekoGui::profileManager-> CurrentGroup ()-> GetProfileEnts (), out, true , false );
NekoGui::ProfileFilter::OnlyInSrc_ByPointer (NekoGui::profileManager-> CurrentGroup ()-> GetProfileEnts (), out, out_del);
int remove_display_count = 0 ;
QString remove_display;
@ -1539,9 +1471,9 @@ void MainWindow::on_menu_delete_repeat_triggered () {
if (!out_del.empty() &&
QMessageBox::question ( this , tr ( " Confirmation " ), tr ( " Remove %1 item(s) ? " ). arg (out_del. length ()) + " \n " + remove_display) == QMessageBox::StandardButton::Yes) {
for ( const auto &ent: out_del) {
NekoGui::profileManager-> DeleteProfile (ent-> id );
NekoGui::profileManager->DeleteProfile(ent-> id );
}
refresh_proxy_list ();
refresh_proxy_list();
}
}
@ -1868,7 +1800,7 @@ void MainWindow::on_menu_remove_invalid_triggered() {
auto currentGroup = NekoGui::profileManager->GetGroup(NekoGui::dataStore->current_group);
if (currentGroup == nullptr) return;
for (const auto &profile : currentGroup->Profiles()) {
for (const auto &profile : currentGroup->GetProfileEnts()) {
if (!IsValid(profile)) out_del += profile;
}
@ -1927,7 +1859,8 @@ void MainWindow::on_menu_resolve_domain_triggered() {
auto resolve_count = std::atomic<int>(0);
NekoGui::dataStore->resolve_count = profiles.count();
for (const auto &profile: profiles) {
for (const auto id: profiles) {
auto profile = NekoGui::profileManager->GetProfile(id);
profile->bean->ResolveDomainToIP([=] {
profile->Save();
if (--NekoGui::dataStore->resolve_count != 0) return;
@ -1957,9 +1890,9 @@ QList<std::shared_ptr<NekoGui::ProxyEntity>> MainWindow::get_selected_or_group()
QList<std::shared_ptr<NekoGui::ProxyEntity>> profiles;
if (selected_or_group > 0) {
profiles = get_now_selected_list();
if (profiles.isEmpty() && selected_or_group == 2) profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder();
if (profiles.isEmpty() && selected_or_group == 2) profiles = NekoGui::profileManager->CurrentGroup()->GetProfileEnts();
} else {
profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder();
profiles = NekoGui::profileManager->CurrentGroup()->GetProfileEnts();
}
return profiles;
}

View File

@ -564,7 +564,7 @@ void DialogEditProfile::do_apply_to_group(const std::shared_ptr<NekoGui::Group>
auto stream = GetStreamSettings(ent->bean.get());
auto copyStream = [=](void *p) {
for (const auto &profile: group->Profiles()) {
for (const auto &profile: group->GetProfileEnts()) {
auto newStream = GetStreamSettings(profile->bean.get());
if (newStream == nullptr) continue;
if (stream == newStream) continue;
@ -575,7 +575,7 @@ void DialogEditProfile::do_apply_to_group(const std::shared_ptr<NekoGui::Group>
};
auto copyBean = [=](void *p) {
for (const auto &profile: group->Profiles()) {
for (const auto &profile: group->GetProfileEnts()) {
if (profile == ent) continue;
profile->bean->_setValue(ent->bean->_name(p), p);
// qDebug() << profile->bean->ToJsonBytes();