diff --git a/db/Database.cpp b/db/Database.cpp index eb05f31..2295ba8 100644 --- a/db/Database.cpp +++ b/db/Database.cpp @@ -3,6 +3,7 @@ #include "fmt/includes.h" #include +#include #include namespace NekoGui { @@ -10,34 +11,80 @@ namespace NekoGui { ProfileManager *profileManager = new ProfileManager(); ProfileManager::ProfileManager() : JsonStore("groups/pm.json") { - callback_after_load = [this]() { LoadManager(); }; - callback_before_save = [this]() { SaveManager(); }; - _add(new configItem("profiles", &_profiles, itemType::integerList)); - _add(new configItem("groups", &_groups, itemType::integerList)); + _add(new configItem("groups", &groupsTabOrder, itemType::integerList)); + } + + QList filterIntJsonFile(const QString &path) { + QList result; + QDir dr(path); + auto entryList = dr.entryList(QDir::Files); + for (auto e: entryList) { + e = e.toLower(); + if (!e.endsWith(".json", Qt::CaseInsensitive)) continue; + e = e.remove(".json", Qt::CaseInsensitive); + bool ok; + auto id = e.toInt(&ok); + if (ok) { + result << id; + } + } + std::sort(result.begin(), result.end()); + return result; } void ProfileManager::LoadManager() { + JsonStore::Load(); + // profiles = {}; groups = {}; - QList invalidProfileId; - for (auto id: _profiles) { + profilesIdOrder = filterIntJsonFile("profiles"); + groupsIdOrder = filterIntJsonFile("groups"); + // Load Proxys + QList delProfile; + for (auto id: profilesIdOrder) { auto ent = LoadProxyEntity(QString("profiles/%1.json").arg(id)); + // Corrupted profile? if (ent == nullptr || ent->bean == nullptr || ent->bean->version == -114514) { - // clear invalid profile - invalidProfileId << id; + delProfile << id; continue; } profiles[id] = ent; } - for (auto id: _groups) { - groups[id] = LoadGroup(QString("groups/%1.json").arg(id)); - } - for (auto id: invalidProfileId) { + // Clear Corrupted profile + for (auto id: delProfile) { DeleteProfile(id); } + // Load Groups + auto loadedOrder = groupsTabOrder; + groupsTabOrder = {}; + for (auto id: groupsIdOrder) { + auto ent = LoadGroup(QString("groups/%1.json").arg(id)); + // Corrupted group? + if (ent->id != id) { + continue; + } + // Ensure order contains every group + if (!loadedOrder.contains(id)) { + loadedOrder << id; + } + groups[id] = ent; + } + // Ensure groups contains order + for (auto id: loadedOrder) { + if (groups.count(id)) { + groupsTabOrder << id; + } + } + // First setup + if (groups.empty()) { + auto defaultGroup = NekoGui::ProfileManager::NewGroup(); + defaultGroup->name = QObject::tr("Default"); + NekoGui::profileManager->AddGroup(defaultGroup); + } } void ProfileManager::SaveManager() { + JsonStore::Save(); } std::shared_ptr ProfileManager::LoadProxyEntity(const QString &jsonPath) { @@ -153,7 +200,7 @@ namespace NekoGui { if (profiles.empty()) { return 0; } else { - return profiles.lastKey() + 1; + return profilesIdOrder.last() + 1; } } @@ -165,8 +212,7 @@ namespace NekoGui { ent->gid = gid < 0 ? dataStore->current_group : gid; ent->id = NewProfileID(); profiles[ent->id] = ent; - _profiles.push_back(ent->id); - Save(); + profilesIdOrder.push_back(ent->id); ent->fn = QString("profiles/%1.json").arg(ent->id); ent->Save(); @@ -176,9 +222,8 @@ namespace NekoGui { void ProfileManager::DeleteProfile(int id) { if (id < 0) return; if (dataStore->started_id == id) return; - profiles.remove(id); - _profiles.removeAll(id); - Save(); + profiles.erase(id); + profilesIdOrder.removeAll(id); QFile(QString("profiles/%1.json").arg(id)).remove(); } @@ -199,7 +244,7 @@ namespace NekoGui { } std::shared_ptr ProfileManager::GetProfile(int id) { - return profiles.value(id, nullptr); + return profiles.count(id) ? profiles[id] : nullptr; } // Group @@ -228,7 +273,7 @@ namespace NekoGui { if (groups.empty()) { return 0; } else { - return groups.lastKey() + 1; + return groupsIdOrder.last() + 1; } } @@ -239,8 +284,8 @@ namespace NekoGui { ent->id = NewGroupID(); groups[ent->id] = ent; - _groups.push_back(ent->id); - Save(); + groupsIdOrder.push_back(ent->id); + groupsTabOrder.push_back(ent->id); ent->fn = QString("groups/%1.json").arg(ent->id); ent->Save(); @@ -248,22 +293,22 @@ namespace NekoGui { } void ProfileManager::DeleteGroup(int gid) { - if (groups.count() == 1) return; + if (groups.size() <= 1) return; QList toDelete; - for (const auto &profile: profiles) { - if (profile->gid == gid) toDelete += profile->id; // map访问中,不能操作 + for (const auto &[id, profile]: profiles) { + if (profile->gid == gid) toDelete += id; // map访问中,不能操作 } for (const auto &id: toDelete) { DeleteProfile(id); } - groups.remove(gid); - _groups.removeAll(gid); - Save(); + groups.erase(gid); + groupsIdOrder.removeAll(gid); + groupsTabOrder.removeAll(gid); QFile(QString("groups/%1.json").arg(gid)).remove(); } std::shared_ptr ProfileManager::GetGroup(int id) { - return groups.value(id, nullptr); + return groups.count(id) ? groups[id] : nullptr; } std::shared_ptr ProfileManager::CurrentGroup() { @@ -272,8 +317,8 @@ namespace NekoGui { QList> Group::Profiles() const { QList> ret; - for (const auto &ent: profileManager->profiles) { - if (id == ent->gid) ret += ent; + for (const auto &[_, profile]: profileManager->profiles) { + if (id == profile->gid) ret += profile; } return ret; } diff --git a/db/Database.hpp b/db/Database.hpp index e888fe9..dc7272f 100644 --- a/db/Database.hpp +++ b/db/Database.hpp @@ -5,18 +5,25 @@ #include "Group.hpp" namespace NekoGui { - class ProfileManager : public JsonStore { + class ProfileManager : private JsonStore { public: - // Manager - QMap> profiles; - QMap> groups; + // JsonStore - // JSON - QList _profiles; - QList _groups; // with order + // order -> id + QList groupsTabOrder; + + // Manager + + std::map> profiles; + std::map> groups; ProfileManager(); + // LoadManager Reset and loads profiles & groups + void LoadManager(); + + void SaveManager(); + [[nodiscard]] static std::shared_ptr NewProxyEntity(const QString &type); [[nodiscard]] static std::shared_ptr NewGroup(); @@ -38,9 +45,9 @@ namespace NekoGui { std::shared_ptr CurrentGroup(); private: - void LoadManager(); - - void SaveManager(); + // sort by id + QList profilesIdOrder; + QList groupsIdOrder; [[nodiscard]] int NewProfileID() const; diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index 15e0c82..154ecd8 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -348,13 +348,8 @@ namespace NekoGui { } QStringList Routing::List() { - QStringList l; - QDir d; - if (d.exists(ROUTES_PREFIX)) { - QDir dr(ROUTES_PREFIX); - return dr.entryList(QDir::Files); - } - return l; + QDir dr(ROUTES_PREFIX); + return dr.entryList(QDir::Files); } void Routing::SetToActive(const QString &name) { diff --git a/rpc/gRPC.cpp b/rpc/gRPC.cpp index 7b7f340..c07ad23 100644 --- a/rpc/gRPC.cpp +++ b/rpc/gRPC.cpp @@ -58,11 +58,6 @@ namespace QtGrpc { QString serviceName; QByteArray nekoray_auth; - // TODO Fixed? - // https://github.com/semlanik/qtprotobuf/issues/116 - // setCachingEnabled: 5 bytesDownloaded - // QNetworkReplyImpl: backend error: caching was enabled after some bytes had been written - // async QNetworkReply *post(const QString &method, const QString &service, const QByteArray &args) { QUrl callUrl = url_base + "/" + service + "/" + method; diff --git a/translations/fa_IR.ts b/translations/fa_IR.ts index abd8afa..98a4b16 100644 --- a/translations/fa_IR.ts +++ b/translations/fa_IR.ts @@ -1301,7 +1301,7 @@ This needs to be run NekoBox with administrator privileges. Default - پیش فرض + پیش فرض Load routing and apply: %1 @@ -1650,6 +1650,10 @@ Direct: %2 As Subscription (add to this group) + + Default + پیش فرض + Qv2ray::ui::widgets::AutoCompleteTextEdit diff --git a/translations/ru_RU.ts b/translations/ru_RU.ts index 7a96af6..c0d0531 100644 --- a/translations/ru_RU.ts +++ b/translations/ru_RU.ts @@ -1314,7 +1314,7 @@ https://matsuridayo.github.io/n-configuration/#vpn-tun Default - По умолчанию + По умолчанию Load routing and apply: %1 @@ -1646,6 +1646,10 @@ Release note: Used: %1 Remain: %2 Expire: %3 Использовано: %1, осталось: %2, истекло: %3 + + Default + По умолчанию + Qv2ray::ui::widgets::AutoCompleteTextEdit diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index 604e69e..b80c543 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -1151,7 +1151,7 @@ This needs to be run NekoBox with administrator privileges. Default - 默认 + 默认 Confirmation @@ -1644,6 +1644,10 @@ Release note: As Subscription (add to this group) 作为订阅(添加到该组) + + Default + 默认 + Qv2ray::ui::widgets::AutoCompleteTextEdit diff --git a/ui/dialog_manage_groups.cpp b/ui/dialog_manage_groups.cpp index 7a86b08..e86867e 100644 --- a/ui/dialog_manage_groups.cpp +++ b/ui/dialog_manage_groups.cpp @@ -24,7 +24,7 @@ DialogManageGroups::DialogManageGroups(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageGroups) { ui->setupUi(this); - for (auto id: NekoGui::profileManager->_groups) { + for (auto id: NekoGui::profileManager->groupsTabOrder) { AddGroupToListIfExist(id) } @@ -53,10 +53,10 @@ void DialogManageGroups::on_add_clicked() { void DialogManageGroups::on_update_all_clicked() { if (QMessageBox::question(this, tr("Confirmation"), tr("Update all subscriptions?")) == QMessageBox::StandardButton::Yes) { - for (const auto &gid: NekoGui::profileManager->_groups) { + for (const auto &gid: NekoGui::profileManager->groupsTabOrder) { auto group = NekoGui::profileManager->GetGroup(gid); if (group == nullptr || group->url.isEmpty()) continue; - UI_update_one_group(NekoGui::profileManager->_groups.indexOf(gid)); + UI_update_one_group(NekoGui::profileManager->groupsTabOrder.indexOf(gid)); return; } } @@ -68,15 +68,15 @@ void UI_update_one_group(int _order) { std::shared_ptr nextGroup; forever { nextOrder += 1; - if (nextOrder >= NekoGui::profileManager->_groups.length()) break; - auto nextGid = NekoGui::profileManager->_groups[nextOrder]; + if (nextOrder >= NekoGui::profileManager->groupsTabOrder.size()) break; + auto nextGid = NekoGui::profileManager->groupsTabOrder[nextOrder]; nextGroup = NekoGui::profileManager->GetGroup(nextGid); if (nextGroup == nullptr || nextGroup->url.isEmpty()) continue; break; } // calculate this group - auto group = NekoGui::profileManager->GetGroup(NekoGui::profileManager->_groups[_order]); + auto group = NekoGui::profileManager->GetGroup(NekoGui::profileManager->groupsTabOrder[_order]); if (group == nullptr) return; // v2.2: listener is moved to GroupItem, no refresh here. diff --git a/ui/edit/dialog_edit_group.cpp b/ui/edit/dialog_edit_group.cpp index e86f002..22cbea7 100644 --- a/ui/edit/dialog_edit_group.cpp +++ b/ui/edit/dialog_edit_group.cpp @@ -44,7 +44,7 @@ DialogEditGroup::DialogEditGroup(const std::shared_ptr &ent, QWi connect(ui->copy_links, &QPushButton::clicked, this, [=] { QStringList links; - for (const auto &profile: NekoGui::profileManager->profiles) { + for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (profile->gid != ent->id) continue; links += profile->bean->ToShareLink(); } @@ -53,7 +53,7 @@ DialogEditGroup::DialogEditGroup(const std::shared_ptr &ent, QWi }); connect(ui->copy_links_nkr, &QPushButton::clicked, this, [=] { QStringList links; - for (const auto &profile: NekoGui::profileManager->profiles) { + for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (profile->gid != ent->id) continue; links += profile->bean->ToNekorayShareLink(profile->type); } diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 01a9b93..0942456 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -57,12 +57,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi }; // Load Manager - auto isLoaded = NekoGui::profileManager->Load(); - if (!isLoaded) { - auto defaultGroup = NekoGui::ProfileManager::NewGroup(); - defaultGroup->name = tr("Default"); - NekoGui::profileManager->AddGroup(defaultGroup); - } + NekoGui::profileManager->LoadManager(); // Setup misc UI themeManager->ApplyTheme(NekoGui::dataStore->theme); @@ -72,11 +67,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi connect(ui->menu_stop, &QAction::triggered, this, [=]() { neko_stop(); }); connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, [=](int from, int to) { // use tabData to track tab & gid - NekoGui::profileManager->_groups.clear(); + NekoGui::profileManager->groupsTabOrder.clear(); for (int i = 0; i < ui->tabWidget->tabBar()->count(); i++) { - NekoGui::profileManager->_groups += ui->tabWidget->tabBar()->tabData(i).toInt(); + NekoGui::profileManager->groupsTabOrder += ui->tabWidget->tabBar()->tabData(i).toInt(); } - NekoGui::profileManager->Save(); + NekoGui::profileManager->SaveManager(); }); ui->label_running->installEventFilter(this); ui->label_inbound->installEventFilter(this); @@ -458,13 +453,13 @@ MainWindow::~MainWindow() { // Group tab manage inline int tabIndex2GroupId(int index) { - if (NekoGui::profileManager->_groups.length() <= index) return -1; - return NekoGui::profileManager->_groups[index]; + if (NekoGui::profileManager->groupsTabOrder.length() <= index) return -1; + return NekoGui::profileManager->groupsTabOrder[index]; } inline int groupId2TabIndex(int gid) { - for (int key = 0; key < NekoGui::profileManager->_groups.count(); key++) { - if (NekoGui::profileManager->_groups[key] == gid) return key; + for (int key = 0; key < NekoGui::profileManager->groupsTabOrder.count(); key++) { + if (NekoGui::profileManager->groupsTabOrder[key] == gid) return key; } return 0; } @@ -646,6 +641,7 @@ void MainWindow::on_commitDataRequest() { } // NekoGui::dataStore->Save(); + NekoGui::profileManager->SaveManager(); qDebug() << "End of data save"; } @@ -903,7 +899,7 @@ void MainWindow::refresh_groups() { } int index = 0; - for (const auto &gid: NekoGui::profileManager->_groups) { + for (const auto &gid: NekoGui::profileManager->groupsTabOrder) { auto group = NekoGui::profileManager->GetGroup(gid); if (index == 0) { ui->tabWidget->setTabText(0, group->name); @@ -923,7 +919,7 @@ void MainWindow::refresh_groups() { if (NekoGui::profileManager->CurrentGroup() == nullptr) { NekoGui::dataStore->current_group = -1; ui->tabWidget->setCurrentIndex(groupId2TabIndex(0)); - show_group(NekoGui::profileManager->_groups.count() > 0 ? NekoGui::profileManager->_groups.first() : 0); + show_group(NekoGui::profileManager->groupsTabOrder.count() > 0 ? NekoGui::profileManager->groupsTabOrder.first() : 0); } else { ui->tabWidget->setCurrentIndex(groupId2TabIndex(NekoGui::dataStore->current_group)); show_group(NekoGui::dataStore->current_group); @@ -944,11 +940,11 @@ void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSor ui->proxyListTable->setRowCount(0); // 添加行 int row = -1; - for (const auto &profile: NekoGui::profileManager->profiles) { + for (const auto &[id, profile]: NekoGui::profileManager->profiles) { if (NekoGui::dataStore->current_group != profile->gid) continue; row++; ui->proxyListTable->insertRow(row); - ui->proxyListTable->row2Id += profile->id; + ui->proxyListTable->row2Id += id; } } @@ -1121,7 +1117,7 @@ void MainWindow::on_menu_move_triggered() { if (ents.isEmpty()) return; auto items = QStringList{}; - for (auto gid: NekoGui::profileManager->_groups) { + for (auto gid: NekoGui::profileManager->groupsTabOrder) { auto group = NekoGui::profileManager->GetGroup(gid); if (group == nullptr) continue; items += Int2String(gid) + " " + group->name; @@ -1170,7 +1166,7 @@ void MainWindow::on_menu_profile_debug_info_triggered() { QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(QString("profiles/%1.json").arg(ents.first()->id)).absoluteFilePath())); } else if (btn == 2) { NekoGui::dataStore->Load(); - NekoGui::profileManager->Load(); + NekoGui::profileManager->LoadManager(); refresh_proxy_list(); } } @@ -1398,7 +1394,7 @@ void MainWindow::on_menu_update_subscription_triggered() { void MainWindow::on_menu_remove_unavailable_triggered() { QList> out_del; - for (const auto &profile: NekoGui::profileManager->profiles) { + for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (NekoGui::dataStore->current_group != profile->gid) continue; if (profile->latency < 0) out_del += profile; } diff --git a/ui/widget/GroupItem.cpp b/ui/widget/GroupItem.cpp index 195ee98..d3ff39d 100644 --- a/ui/widget/GroupItem.cpp +++ b/ui/widget/GroupItem.cpp @@ -116,7 +116,7 @@ void GroupItem::on_edit_clicked() { } void GroupItem::on_remove_clicked() { - if (NekoGui::profileManager->groups.count() == 1) return; + if (NekoGui::profileManager->groups.size() <= 1) return; if (QMessageBox::question(this, tr("Confirmation"), tr("Remove %1?").arg(ent->name)) == QMessageBox::StandardButton::Yes) { NekoGui::profileManager->DeleteGroup(ent->id);