diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 81bf157..3b275f2 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -1061,7 +1061,6 @@ namespace NekoGui { auto configFn = ":/neko/vpn/sing-box-vpn.json"; if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json"; auto config = ReadFileText(configFn) - .replace("%ENABLED_FAKEDNS%", dataStore->fake_dns ? "true" : "false") .replace("//%IPV6_ADDRESS%", dataStore->vpn_ipv6 ? R"("inet6_address": "fdfe:dcba:9876::1/126",)" : "") .replace("//%SOCKS_USER_PASS%", socks_user_pass) .replace("//%PROCESS_NAME_RULE%", process_name_rule) @@ -1072,6 +1071,7 @@ namespace NekoGui { .replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false") .replace("%FINAL_OUT%", no_match_out) .replace("%DNS_ADDRESS%", BOX_UNDERLYING_DNS) + .replace("%FAKE_DNS_ENABLE%", dataStore->fake_dns ? "true" : "false") .replace("%FAKE_DNS_INBOUND%", dataStore->fake_dns ? "tun-in" : "empty") .replace("%PORT%", Int2String(dataStore->inbound_socks_port)); // hook.js diff --git a/db/Database.cpp b/db/Database.cpp index 941f9e1..d0e7f7b 100644 --- a/db/Database.cpp +++ b/db/Database.cpp @@ -305,6 +305,7 @@ namespace NekoGui { _add(new configItem("id", &id, itemType::integer)); _add(new configItem("front_proxy_id", &front_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)); diff --git a/db/Group.hpp b/db/Group.hpp index 1dfd743..e55c680 100644 --- a/db/Group.hpp +++ b/db/Group.hpp @@ -8,6 +8,7 @@ namespace NekoGui { public: int id = -1; bool archive = false; + bool skip_auto_update = false; QString name = ""; QString url = ""; QString info = ""; diff --git a/main/HTTPRequestHelper.cpp b/main/HTTPRequestHelper.cpp index 7f081bc..a43010f 100644 --- a/main/HTTPRequestHelper.cpp +++ b/main/HTTPRequestHelper.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "main/NekoGui.hpp" @@ -52,12 +53,21 @@ namespace NekoGui_network { } MW_show_log(QString("SSL Errors: %1 %2").arg(error_str.join(","), NekoGui::dataStore->sub_insecure ? "(Ignored)" : "")); }); - // + // Wait for response + auto abortTimer = new QTimer; + abortTimer->setSingleShot(true); + abortTimer->setInterval(10000); + QObject::connect(abortTimer, &QTimer::timeout, _reply, &QNetworkReply::abort); + abortTimer->start(); { QEventLoop loop; - QObject::connect(&accessManager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + QObject::connect(_reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); } + if (abortTimer != nullptr) { + abortTimer->stop(); + abortTimer->deleteLater(); + } // auto result = NekoHTTPResponse{_reply->error() == QNetworkReply::NetworkError::NoError ? "" : _reply->errorString(), _reply->readAll(), _reply->rawHeaderPairs()}; diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index 939b187..b483f05 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -271,6 +271,7 @@ namespace NekoGui { _add(new configItem("sp_format", &system_proxy_format, itemType::string)); _add(new configItem("sub_clear", &sub_clear, itemType::boolean)); _add(new configItem("sub_insecure", &sub_insecure, itemType::boolean)); + _add(new configItem("sub_auto_update", &sub_auto_update, itemType::integer)); _add(new configItem("enable_js_hook", &enable_js_hook, itemType::integer)); _add(new configItem("log_ignore", &log_ignore, itemType::stringList)); _add(new configItem("start_minimal", &start_minimal, itemType::boolean)); diff --git a/main/NekoGui_DataStore.hpp b/main/NekoGui_DataStore.hpp index 3f1a40f..78c1a12 100644 --- a/main/NekoGui_DataStore.hpp +++ b/main/NekoGui_DataStore.hpp @@ -121,6 +121,7 @@ namespace NekoGui { bool sub_use_proxy = false; bool sub_clear = false; bool sub_insecure = false; + int sub_auto_update = -30; // Security bool skip_cert = false; diff --git a/main/NekoGui_Utils.hpp b/main/NekoGui_Utils.hpp index ab2d750..034dc1f 100644 --- a/main/NekoGui_Utils.hpp +++ b/main/NekoGui_Utils.hpp @@ -28,6 +28,12 @@ inline std::function MW_dialog_message; class QThread; inline QThread *DS_cores; +// Timers + +class QTimer; +inline QTimer *TM_auto_update_subsctiption; +inline std::function TM_auto_update_subsctiption_Reset_Minute; + // String #define FIRST_OR_SECOND(a, b) a.isEmpty() ? b : a diff --git a/res/vpn/sing-box-vpn.json b/res/vpn/sing-box-vpn.json index 6279183..1b3b365 100644 --- a/res/vpn/sing-box-vpn.json +++ b/res/vpn/sing-box-vpn.json @@ -4,7 +4,7 @@ }, "dns": { "fakeip": { - "enabled": %ENABLED_FAKEDNS%, + "enabled": %FAKE_DNS_ENABLE%, "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" }, diff --git a/rpc/gRPC.cpp b/rpc/gRPC.cpp index c07ad23..1dc7c3d 100644 --- a/rpc/gRPC.cpp +++ b/rpc/gRPC.cpp @@ -114,9 +114,7 @@ namespace QtGrpc { abortTimer = new QTimer; abortTimer->setSingleShot(true); abortTimer->setInterval(timeout_ms); - QObject::connect(abortTimer, &QTimer::timeout, abortTimer, [=]() { - networkReply->abort(); - }); + QObject::connect(abortTimer, &QTimer::timeout, networkReply, &QNetworkReply::abort); abortTimer->start(); } diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp index 089e50d..5d71e2f 100644 --- a/sub/GroupUpdater.cpp +++ b/sub/GroupUpdater.cpp @@ -561,3 +561,53 @@ namespace NekoGui_sub { } } } // namespace NekoGui_sub + +bool UI_update_all_groups_Updating = false; + +#define should_skip_group(g) (g == nullptr || g->url.isEmpty() || g->archive || (onlyAllowed && g->skip_auto_update)) + +void serialUpdateSubscription(const QList &groupsTabOrder, int _order, bool onlyAllowed) { + // calculate next group + int nextOrder = _order; + std::shared_ptr nextGroup; + forever { + nextOrder += 1; + if (nextOrder >= groupsTabOrder.size()) break; + auto nextGid = groupsTabOrder[nextOrder]; + nextGroup = NekoGui::profileManager->GetGroup(nextGid); + if (should_skip_group(nextGroup)) continue; + break; + } + + // calculate this group + auto group = NekoGui::profileManager->GetGroup(groupsTabOrder[_order]); + if (group == nullptr) { + UI_update_all_groups_Updating = false; + return; + } + + // v2.2: listener is moved to GroupItem, no refresh here. + NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [=] { + if (nextGroup == nullptr) { + UI_update_all_groups_Updating = false; + } else { + serialUpdateSubscription(groupsTabOrder, nextOrder, onlyAllowed); + } + }); +} + +void UI_update_all_groups(bool onlyAllowed) { + if (UI_update_all_groups_Updating) { + MW_show_log("The last subscription update has not exited."); + return; + } + // first: freeze group order + auto groupsTabOrder = NekoGui::profileManager->groupsTabOrder; + for (const auto &gid: groupsTabOrder) { + auto group = NekoGui::profileManager->GetGroup(gid); + if (should_skip_group(group)) continue; + // start + UI_update_all_groups_Updating = true; + serialUpdateSubscription(groupsTabOrder, groupsTabOrder.indexOf(gid), onlyAllowed); + } +} diff --git a/sub/GroupUpdater.hpp b/sub/GroupUpdater.hpp index 20d380c..91b2526 100644 --- a/sub/GroupUpdater.hpp +++ b/sub/GroupUpdater.hpp @@ -29,3 +29,6 @@ namespace NekoGui_sub { extern GroupUpdater *groupUpdater; } // namespace NekoGui_sub + +// 更新所有订阅 关闭分组窗口时 更新动作继续执行 +void UI_update_all_groups(bool onlyAllowed = false); diff --git a/translations/fa_IR.ts b/translations/fa_IR.ts index 8e2f681..dd49f38 100644 --- a/translations/fa_IR.ts +++ b/translations/fa_IR.ts @@ -247,6 +247,14 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod Timeout (s) + + Automatic update + + + + Interval (minute, invalid if less than 30) + + DialogEditGroup @@ -314,6 +322,18 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod Clear پاک کردن + + Skip automatic update + + + + Common + متداول + + + Share + اشتراک گذاری + DialogEditProfile diff --git a/translations/ru_RU.ts b/translations/ru_RU.ts index 1e78b6d..7c9d86d 100644 --- a/translations/ru_RU.ts +++ b/translations/ru_RU.ts @@ -243,6 +243,14 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod Timeout (s) + + Automatic update + + + + Interval (minute, invalid if less than 30) + + DialogEditGroup @@ -310,6 +318,18 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod None Нет + + Skip automatic update + + + + Common + Общие + + + Share + Поделиться + DialogEditProfile diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index ae82954..4eeff52 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -243,6 +243,14 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod Timeout (s) 超时(秒) + + Automatic update + 自动更新订阅 + + + Interval (minute, invalid if less than 30) + 时间间隔(分钟,少于 30 分钟无效) + DialogEditGroup @@ -310,6 +318,18 @@ For NekoBox, this rewrites the underlying(localhost) DNS in Tun Mode, normal mod Clear 清除 + + Skip automatic update + 跳过自动更新 + + + Common + 通用 + + + Share + 分享 + DialogEditProfile diff --git a/ui/dialog_basic_settings.cpp b/ui/dialog_basic_settings.cpp index b5b6189..156423a 100644 --- a/ui/dialog_basic_settings.cpp +++ b/ui/dialog_basic_settings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include class ExtraCoreWidget : public QWidget { public: @@ -158,6 +159,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent) D_LOAD_BOOL(sub_use_proxy) D_LOAD_BOOL(sub_clear) D_LOAD_BOOL(sub_insecure) + D_LOAD_INT_ENABLE(sub_auto_update, sub_auto_update_enable) // Core @@ -298,10 +300,17 @@ void DialogBasicSettings::accept() { // Subscription + if (ui->sub_auto_update_enable->isChecked()) { + TM_auto_update_subsctiption_Reset_Minute(ui->sub_auto_update->text().toInt()); + } else { + TM_auto_update_subsctiption_Reset_Minute(0); + } + NekoGui::dataStore->user_agent = ui->user_agent->text(); D_SAVE_BOOL(sub_use_proxy) D_SAVE_BOOL(sub_clear) D_SAVE_BOOL(sub_insecure) + D_SAVE_INT_ENABLE(sub_auto_update, sub_auto_update_enable) // Core diff --git a/ui/dialog_basic_settings.ui b/ui/dialog_basic_settings.ui index 18626c6..df407c2 100644 --- a/ui/dialog_basic_settings.ui +++ b/ui/dialog_basic_settings.ui @@ -486,37 +486,84 @@ Subscription - - - - User Agent - - - - + + + + + + 0 + 0 + + + + Enable + + + + + + + Interval (minute, invalid if less than 30) + + + + + + + + 0 + 0 + + + + + + + + Use proxy when updating subscription - + Ignore TLS errors when updating subscription - + Clear servers before updating subscription + + + + + 0 + 0 + + + + Automatic update + + + + + + + User Agent + + + diff --git a/ui/dialog_manage_groups.cpp b/ui/dialog_manage_groups.cpp index e86867e..0cf3c8b 100644 --- a/ui/dialog_manage_groups.cpp +++ b/ui/dialog_manage_groups.cpp @@ -53,34 +53,6 @@ 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->groupsTabOrder) { - auto group = NekoGui::profileManager->GetGroup(gid); - if (group == nullptr || group->url.isEmpty()) continue; - UI_update_one_group(NekoGui::profileManager->groupsTabOrder.indexOf(gid)); - return; - } + UI_update_all_groups(); } } - -void UI_update_one_group(int _order) { - // calculate next group - int nextOrder = _order; - std::shared_ptr nextGroup; - forever { - nextOrder += 1; - 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->groupsTabOrder[_order]); - if (group == nullptr) return; - - // v2.2: listener is moved to GroupItem, no refresh here. - NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [=] { - if (nextGroup != nullptr) UI_update_one_group(nextOrder); - }); -} diff --git a/ui/dialog_manage_groups.h b/ui/dialog_manage_groups.h index 7f816b8..04e4412 100644 --- a/ui/dialog_manage_groups.h +++ b/ui/dialog_manage_groups.h @@ -30,7 +30,3 @@ private slots: void on_update_all_clicked(); }; - -// 更新所有订阅 关闭分组窗口时 更新动作继续执行 - -void UI_update_one_group(int _order); diff --git a/ui/edit/dialog_edit_group.cpp b/ui/edit/dialog_edit_group.cpp index 22cbea7..061f654 100644 --- a/ui/edit/dialog_edit_group.cpp +++ b/ui/edit/dialog_edit_group.cpp @@ -6,28 +6,30 @@ #include +#define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this); + DialogEditGroup::DialogEditGroup(const std::shared_ptr &ent, QWidget *parent) : QDialog(parent), ui(new Ui::DialogEditGroup) { ui->setupUi(this); this->ent = ent; connect(ui->type, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { ui->cat_sub->setHidden(index == 0); + ADJUST_SIZE }); ui->name->setText(ent->name); ui->archive->setChecked(ent->archive); + ui->skip_auto_update->setChecked(ent->skip_auto_update); ui->url->setText(ent->url); ui->type->setCurrentIndex(ent->url.isEmpty() ? 0 : 1); ui->type->currentIndexChanged(ui->type->currentIndex()); ui->manually_column_width->setChecked(ent->manually_column_width); - ui->copy_links->setVisible(false); - ui->copy_links_nkr->setVisible(false); + ui->cat_share->setVisible(false); if (ent->id >= 0) { // already a group ui->type->setDisabled(true); if (!ent->Profiles().isEmpty()) { - ui->copy_links->setVisible(true); - ui->copy_links_nkr->setVisible(true); + ui->cat_share->setVisible(true); } } else { // new group ui->front_proxy->hide(); @@ -60,6 +62,8 @@ DialogEditGroup::DialogEditGroup(const std::shared_ptr &ent, QWi QApplication::clipboard()->setText(links.join("\n")); MessageBoxInfo(software_name, tr("Copied")); }); + + ADJUST_SIZE } DialogEditGroup::~DialogEditGroup() { @@ -76,6 +80,7 @@ void DialogEditGroup::accept() { ent->name = ui->name->text(); ent->url = ui->url->text(); ent->archive = ui->archive->isChecked(); + ent->skip_auto_update = ui->skip_auto_update->isChecked(); ent->manually_column_width = ui->manually_column_width->isChecked(); ent->front_proxy_id = CACHE.front_proxy; QDialog::accept(); diff --git a/ui/edit/dialog_edit_group.ui b/ui/edit/dialog_edit_group.ui index f736566..e6556c7 100644 --- a/ui/edit/dialog_edit_group.ui +++ b/ui/edit/dialog_edit_group.ui @@ -7,62 +7,34 @@ 0 0 400 - 300 + 468 + + + 400 + 300 + + Edit Group - + 0 0 + + Common + - - 0 - - - 0 - - - 0 - - - 0 - - - - - Name - - - - - - - - - Manually column width - - - - - - - Archive - - - - - @@ -91,6 +63,31 @@ + + + + + + Manually column width + + + + + + + Archive + + + + + + + + + Name + + + @@ -119,60 +116,53 @@ - - - - 0 - 0 - + + + Subscription - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - URL - - - - - - - + + + + + URL + + + + + + + + + + Skip automatic update + + - - - - - Copy profile share links - - - - - - - Copy profile share links (Neko Links) - - - - + + + Share + + + + + + Copy profile share links + + + + + + + Copy profile share links (Neko Links) + + + + + @@ -198,6 +188,7 @@ manually_column_width archive url + skip_auto_update copy_links copy_links_nkr diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 3b6f0e1..d293aeb 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -434,6 +434,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi connect(t, &QTimer::timeout, this, [&] { NekoGui_sys::logCounter.fetchAndStoreRelaxed(0); }); t->start(1000); + TM_auto_update_subsctiption = new QTimer; + TM_auto_update_subsctiption_Reset_Minute = [&](int m) { + TM_auto_update_subsctiption->stop(); + if (m >= 30) TM_auto_update_subsctiption->start(m * 60 * 1000); + }; + connect(TM_auto_update_subsctiption, &QTimer::timeout, this, [&] { UI_update_all_groups(true); }); + TM_auto_update_subsctiption_Reset_Minute(NekoGui::dataStore->sub_auto_update); + if (!NekoGui::dataStore->flag_tray) show(); }