mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2025-12-19 13:42:51 +08:00
feat: Add geo Asset management and Remove pointless features
This commit is contained in:
parent
a85dc6fc41
commit
205830fcc3
@ -1,440 +0,0 @@
|
||||
#include "QvProxyConfigurator.hpp"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
//
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
//
|
||||
#include <wininet.h>
|
||||
#include <ras.h>
|
||||
#include <raserror.h>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QProcess>
|
||||
|
||||
#include "3rdparty/fix_old_qt.h"
|
||||
#include "3rdparty/qv2ray/wrapper.hpp"
|
||||
#include "fmt/Preset.hpp"
|
||||
#include "main/NekoGui.hpp"
|
||||
|
||||
#define QV_MODULE_NAME "SystemProxy"
|
||||
|
||||
#define QSTRN(num) QString::number(num)
|
||||
|
||||
namespace Qv2ray::components::proxy {
|
||||
|
||||
using ProcessArgument = QPair<QString, QStringList>;
|
||||
#ifdef Q_OS_MACOS
|
||||
QStringList macOSgetNetworkServices() {
|
||||
QProcess p;
|
||||
p.setProgram("/usr/sbin/networksetup");
|
||||
p.setArguments(QStringList{"-listallnetworkservices"});
|
||||
p.start();
|
||||
p.waitForStarted();
|
||||
p.waitForFinished();
|
||||
LOG(p.errorString());
|
||||
auto str = p.readAllStandardOutput();
|
||||
auto lines = SplitLines(str);
|
||||
QStringList result;
|
||||
|
||||
// Start from 1 since first line is unneeded.
|
||||
for (auto i = 1; i < lines.count(); i++) {
|
||||
// * means disabled.
|
||||
if (!lines[i].contains("*")) {
|
||||
result << lines[i];
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Found " + QSTRN(result.size()) + " network services: " + result.join(";"));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
#define NO_CONST(expr) const_cast<wchar_t *>(expr)
|
||||
// static auto DEFAULT_CONNECTION_NAME =
|
||||
// NO_CONST(L"DefaultConnectionSettings");
|
||||
///
|
||||
/// INTERNAL FUNCTION
|
||||
bool __QueryProxyOptions() {
|
||||
INTERNET_PER_CONN_OPTION_LIST List;
|
||||
INTERNET_PER_CONN_OPTION Option[5];
|
||||
//
|
||||
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS;
|
||||
Option[2].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
|
||||
//
|
||||
List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
||||
List.pszConnection = nullptr; // NO_CONST(DEFAULT_CONNECTION_NAME);
|
||||
List.dwOptionCount = 5;
|
||||
List.dwOptionError = 0;
|
||||
List.pOptions = Option;
|
||||
|
||||
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
|
||||
LOG("InternetQueryOption failed, GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
LOG("System default proxy info:");
|
||||
|
||||
if (Option[0].Value.pszValue != nullptr) {
|
||||
LOG(QString::fromWCharArray(Option[0].Value.pszValue));
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) {
|
||||
LOG("PROXY_TYPE_AUTO_PROXY_URL");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) {
|
||||
LOG("PROXY_TYPE_AUTO_DETECT");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) {
|
||||
LOG("PROXY_TYPE_DIRECT");
|
||||
}
|
||||
|
||||
if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) {
|
||||
LOG("PROXY_TYPE_PROXY");
|
||||
}
|
||||
|
||||
if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) {
|
||||
LOG("InternetQueryOption failed,GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
if (Option[4].Value.pszValue != nullptr) {
|
||||
LOG(QString::fromStdWString(Option[4].Value.pszValue));
|
||||
}
|
||||
|
||||
INTERNET_VERSION_INFO Version;
|
||||
nSize = sizeof(INTERNET_VERSION_INFO);
|
||||
InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize);
|
||||
|
||||
if (Option[0].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[0].Value.pszValue);
|
||||
}
|
||||
|
||||
if (Option[3].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[3].Value.pszValue);
|
||||
}
|
||||
|
||||
if (Option[4].Value.pszValue != nullptr) {
|
||||
GlobalFree(Option[4].Value.pszValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC) {
|
||||
INTERNET_PER_CONN_OPTION_LIST list;
|
||||
DWORD dwBufSize = sizeof(list);
|
||||
// Fill the list structure.
|
||||
list.dwSize = sizeof(list);
|
||||
// NULL == LAN, otherwise connectoid name.
|
||||
list.pszConnection = nullptr;
|
||||
|
||||
if (nullptr == proxy_full_addr) {
|
||||
LOG("Clearing system proxy");
|
||||
//
|
||||
list.dwOptionCount = 1;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[1];
|
||||
|
||||
// Ensure that the memory was allocated.
|
||||
if (nullptr == list.pOptions) {
|
||||
// Return if the memory wasn't allocated.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT;
|
||||
} else if (isPAC) {
|
||||
LOG("Setting system proxy for PAC");
|
||||
//
|
||||
list.dwOptionCount = 2;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
|
||||
|
||||
if (nullptr == list.pOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL;
|
||||
// Set proxy name.
|
||||
list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL;
|
||||
list.pOptions[1].Value.pszValue = proxy_full_addr;
|
||||
} else {
|
||||
LOG("Setting system proxy for Global Proxy");
|
||||
//
|
||||
list.dwOptionCount = 2;
|
||||
list.pOptions = new INTERNET_PER_CONN_OPTION[2];
|
||||
|
||||
if (nullptr == list.pOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set flags.
|
||||
list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
||||
list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY;
|
||||
// Set proxy name.
|
||||
list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
|
||||
list.pOptions[1].Value.pszValue = proxy_full_addr;
|
||||
// Set proxy override.
|
||||
// list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
|
||||
// auto localhost = L"localhost";
|
||||
// list.pOptions[2].Value.pszValue = NO_CONST(localhost);
|
||||
}
|
||||
|
||||
// Set proxy for LAN.
|
||||
if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {
|
||||
LOG("InternetSetOption failed for LAN, GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
|
||||
RASENTRYNAME entry;
|
||||
entry.dwSize = sizeof(entry);
|
||||
std::vector<RASENTRYNAME> entries;
|
||||
DWORD size = sizeof(entry), count;
|
||||
LPRASENTRYNAME entryAddr = &entry;
|
||||
auto ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);
|
||||
if (ERROR_BUFFER_TOO_SMALL == ret) {
|
||||
entries.resize(count);
|
||||
entries[0].dwSize = sizeof(RASENTRYNAME);
|
||||
entryAddr = entries.data();
|
||||
ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count);
|
||||
}
|
||||
if (ERROR_SUCCESS != ret) {
|
||||
LOG("Failed to list entry names");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set proxy for each connectoid.
|
||||
for (DWORD i = 0; i < count; ++i) {
|
||||
list.pszConnection = entryAddr[i].szEntryName;
|
||||
if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) {
|
||||
LOG("InternetSetOption failed for connectoid " + QString::fromWCharArray(list.pszConnection) + ", GLE=" + QSTRN(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
delete[] list.pOptions;
|
||||
InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0);
|
||||
InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void SetSystemProxy(int httpPort, int socksPort) {
|
||||
const QString &address = "127.0.0.1";
|
||||
bool hasHTTP = (httpPort > 0 && httpPort < 65536);
|
||||
bool hasSOCKS = (socksPort > 0 && socksPort < 65536);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (!hasHTTP) {
|
||||
LOG("Nothing?");
|
||||
return;
|
||||
} else {
|
||||
LOG("Qv2ray will set system proxy to use HTTP");
|
||||
}
|
||||
#else
|
||||
if (!hasHTTP && !hasSOCKS) {
|
||||
LOG("Nothing?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasHTTP) {
|
||||
LOG("Qv2ray will set system proxy to use HTTP");
|
||||
}
|
||||
|
||||
if (hasSOCKS) {
|
||||
LOG("Qv2ray will set system proxy to use SOCKS");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString str = NekoGui::dataStore->system_proxy_format;
|
||||
if (str.isEmpty()) str = Preset::Windows::system_proxy_format[0];
|
||||
str = str.replace("{ip}", address)
|
||||
.replace("{http_port}", Int2String(httpPort))
|
||||
.replace("{socks_port}", Int2String(socksPort));
|
||||
//
|
||||
LOG("Windows proxy string: " + str);
|
||||
auto proxyStrW = new WCHAR[str.length() + 1];
|
||||
wcscpy(proxyStrW, str.toStdWString().c_str());
|
||||
//
|
||||
__QueryProxyOptions();
|
||||
|
||||
if (!__SetProxyOptions(proxyStrW, false)) {
|
||||
LOG("Failed to set proxy.");
|
||||
}
|
||||
|
||||
__QueryProxyOptions();
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<ProcessArgument> actions;
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}};
|
||||
//
|
||||
bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" ||
|
||||
qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma";
|
||||
const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
|
||||
//
|
||||
// Configure HTTP Proxies for HTTP, FTP and HTTPS
|
||||
if (hasHTTP) {
|
||||
// iterate over protocols...
|
||||
for (const auto &protocol: QStringList{"http", "ftp", "https"}) {
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy." + protocol, "host", address}};
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy." + protocol, "port", QSTRN(httpPort)}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", protocol + "Proxy", //
|
||||
"http://" + address + " " + QSTRN(httpPort)}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure SOCKS5 Proxies
|
||||
if (hasSOCKS) {
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy.socks", "host", address}};
|
||||
actions << ProcessArgument{"gsettings",
|
||||
{"set", "org.gnome.system.proxy.socks", "port", QSTRN(socksPort)}};
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "socksProxy", //
|
||||
"socks://" + address + " " + QSTRN(socksPort)}};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Setting Proxy Mode to Manual
|
||||
{
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configPath + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "ProxyType", "1"}};
|
||||
}
|
||||
}
|
||||
|
||||
// Notify kioslaves to reload system proxy configuration.
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"dbus-send",
|
||||
{"--type=signal", "/KIO/Scheduler", //
|
||||
"org.kde.KIO.Scheduler.reparseSlaveConfiguration", //
|
||||
"string:''"}};
|
||||
}
|
||||
// Execute them all!
|
||||
//
|
||||
// note: do not use std::all_of / any_of / none_of,
|
||||
// because those are short-circuit and cannot guarantee atomicity.
|
||||
QList<bool> results;
|
||||
for (const auto &action: actions) {
|
||||
// execute and get the code
|
||||
const auto returnCode = QProcess::execute(action.first, action.second);
|
||||
// print out the commands and result codes
|
||||
DEBUG(QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";")));
|
||||
// give the code back
|
||||
results << (returnCode == QProcess::NormalExit);
|
||||
}
|
||||
|
||||
if (results.count(true) != actions.size()) {
|
||||
LOG("Something wrong when setting proxies.");
|
||||
}
|
||||
#else
|
||||
|
||||
for (const auto &service: macOSgetNetworkServices()) {
|
||||
LOG("Setting proxy for interface: " + service);
|
||||
if (hasHTTP) {
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxy", service, address, QSTRN(httpPort)});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxy", service, address, QSTRN(httpPort)});
|
||||
}
|
||||
|
||||
if (hasSOCKS) {
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "on"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxy", service, address, QSTRN(socksPort)});
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void ClearSystemProxy() {
|
||||
LOG("Clearing System Proxy");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (!__SetProxyOptions(nullptr, false)) {
|
||||
LOG("Failed to clear proxy.");
|
||||
}
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<ProcessArgument> actions;
|
||||
const bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" ||
|
||||
qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma";
|
||||
const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||
|
||||
// Setting System Proxy Mode to: None
|
||||
{
|
||||
// for GNOME:
|
||||
{
|
||||
actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "none"}};
|
||||
}
|
||||
|
||||
// for KDE:
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"kwriteconfig5",
|
||||
{"--file", configRoot + "/kioslaverc", //
|
||||
"--group", "Proxy Settings", //
|
||||
"--key", "ProxyType", "0"}};
|
||||
}
|
||||
}
|
||||
|
||||
// Notify kioslaves to reload system proxy configuration.
|
||||
if (isKDE) {
|
||||
actions << ProcessArgument{"dbus-send",
|
||||
{"--type=signal", "/KIO/Scheduler", //
|
||||
"org.kde.KIO.Scheduler.reparseSlaveConfiguration", //
|
||||
"string:''"}};
|
||||
}
|
||||
|
||||
// Execute the Actions
|
||||
for (const auto &action: actions) {
|
||||
// execute and get the code
|
||||
const auto returnCode = QProcess::execute(action.first, action.second);
|
||||
// print out the commands and result codes
|
||||
DEBUG(QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";")));
|
||||
}
|
||||
|
||||
#else
|
||||
for (const auto &service: macOSgetNetworkServices()) {
|
||||
LOG("Clearing proxy for interface: " + service);
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setautoproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "off"});
|
||||
QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "off"});
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
} // namespace Qv2ray::components::proxy
|
||||
@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
//
|
||||
namespace Qv2ray::components::proxy {
|
||||
void ClearSystemProxy();
|
||||
void SetSystemProxy(int http_port, int socks_port);
|
||||
} // namespace Qv2ray::components::proxy
|
||||
|
||||
using namespace Qv2ray::components;
|
||||
using namespace Qv2ray::components::proxy;
|
||||
@ -1,42 +0,0 @@
|
||||
#include "GeositeReader.hpp"
|
||||
|
||||
#include "3rdparty/qv2ray/wrapper.hpp"
|
||||
#include "picoproto.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QMap>
|
||||
|
||||
namespace Qv2ray::components::GeositeReader {
|
||||
QMap<QString, QStringList> GeositeEntries;
|
||||
|
||||
QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache) {
|
||||
if (GeositeEntries.contains(filepath) && allowCache)
|
||||
return GeositeEntries.value(filepath);
|
||||
|
||||
QStringList list;
|
||||
qInfo() << "Reading geosites from:" << filepath;
|
||||
QFile f(filepath);
|
||||
bool opened = f.open(QFile::OpenModeFlag::ReadOnly);
|
||||
|
||||
if (!opened) {
|
||||
qInfo() << "File cannot be opened:" << filepath;
|
||||
return list;
|
||||
}
|
||||
|
||||
const auto content = f.readAll();
|
||||
f.close();
|
||||
{
|
||||
picoproto::Message root;
|
||||
root.ParseFromBytes((unsigned char *) content.data(), content.size());
|
||||
|
||||
list.reserve(root.GetMessageArray(1).size());
|
||||
for (const auto &geosite: root.GetMessageArray(1))
|
||||
list << QString::fromStdString(geosite->GetString(1));
|
||||
}
|
||||
|
||||
qInfo() << "Loaded" << list.count() << "geosite entries from data file.";
|
||||
list.sort();
|
||||
GeositeEntries[filepath] = list;
|
||||
return list;
|
||||
}
|
||||
} // namespace Qv2ray::components::GeositeReader
|
||||
@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Qv2ray::components::GeositeReader {
|
||||
QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache = true);
|
||||
} // namespace Qv2ray::components::GeositeReader
|
||||
@ -1,551 +0,0 @@
|
||||
/* Copyright 2016 Pete Warden. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==============================================================================*/
|
||||
|
||||
#include "picoproto.hpp"
|
||||
|
||||
namespace picoproto {
|
||||
|
||||
namespace {
|
||||
|
||||
// To keep the dependencies down, here's a local copy of the widespread bit_cast
|
||||
// operator. This is necessary because in practice weird things can happen if
|
||||
// you just try to use reinterpret_cast.
|
||||
template<class Dest, class Source>
|
||||
inline Dest bit_cast(const Source &source) {
|
||||
static_assert(sizeof(Dest) == sizeof(Source), "Sizes do not match");
|
||||
Dest dest;
|
||||
memcpy(&dest, &source, sizeof(dest));
|
||||
return dest;
|
||||
}
|
||||
|
||||
// These are defined in:
|
||||
// https://developers.google.com/protocol-buffers/docs/encoding
|
||||
enum WireType {
|
||||
WIRETYPE_VARINT = 0,
|
||||
WIRETYPE_64BIT = 1,
|
||||
WIRETYPE_LENGTH_DELIMITED = 2,
|
||||
WIRETYPE_GROUP_START = 3,
|
||||
WIRETYPE_GROUP_END = 4,
|
||||
WIRETYPE_32BIT = 5,
|
||||
};
|
||||
|
||||
// Pull bytes from the stream, updating the state.
|
||||
bool ConsumeBytes(uint8_t **current, size_t how_many, size_t *remaining) {
|
||||
if (how_many > *remaining) {
|
||||
PP_LOG(ERROR) << "ReadBytes overrun!";
|
||||
return false;
|
||||
}
|
||||
*current += how_many;
|
||||
*remaining -= how_many;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Grabs a particular type from the byte stream.
|
||||
template<class T>
|
||||
T ReadFromBytes(uint8_t **current, size_t *remaining) {
|
||||
PP_CHECK(ConsumeBytes(current, sizeof(T), remaining));
|
||||
const T result = *(bit_cast<T *>(*current - sizeof(T)));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t ReadVarInt(uint8_t **current, size_t *remaining) {
|
||||
uint64_t result = 0;
|
||||
bool keep_going;
|
||||
int shift = 0;
|
||||
do {
|
||||
const uint8_t next_number = ReadFromBytes<uint8_t>(current, remaining);
|
||||
keep_going = (next_number >= 128);
|
||||
result += (uint64_t) (next_number & 0x7f) << shift;
|
||||
shift += 7;
|
||||
} while (keep_going);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ReadWireTypeAndFieldNumber(uint8_t **current, size_t *remaining, uint8_t *wire_type, uint32_t *field_number) {
|
||||
uint64_t wire_type_and_field_number = ReadVarInt(current, remaining);
|
||||
*wire_type = wire_type_and_field_number & 0x07;
|
||||
*field_number = wire_type_and_field_number >> 3;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string FieldTypeDebugString(enum FieldType type) {
|
||||
switch (type) {
|
||||
case FIELD_UNSET:
|
||||
return "UNSET";
|
||||
break;
|
||||
case FIELD_UINT32:
|
||||
return "UINT32";
|
||||
break;
|
||||
case FIELD_UINT64:
|
||||
return "UINT64";
|
||||
break;
|
||||
case FIELD_BYTES:
|
||||
return "BYTES";
|
||||
break;
|
||||
default:
|
||||
return "Unknown field type";
|
||||
break;
|
||||
}
|
||||
return "Should never get here";
|
||||
}
|
||||
|
||||
Field::Field(FieldType type, bool owns_data) : type(type), owns_data(owns_data) {
|
||||
cached_messages = nullptr;
|
||||
switch (type) {
|
||||
case FIELD_UINT32: {
|
||||
value.v_uint32 = new std::vector<uint32_t>();
|
||||
} break;
|
||||
case FIELD_UINT64: {
|
||||
value.v_uint64 = new std::vector<uint64_t>();
|
||||
} break;
|
||||
case FIELD_BYTES: {
|
||||
value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();
|
||||
cached_messages = new std::vector<Message *>();
|
||||
} break;
|
||||
default: {
|
||||
PP_LOG(ERROR) << "Bad field type when constructing field: " << type;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
Field::Field(const Field &other) : type(other.type), owns_data(other.owns_data) {
|
||||
switch (type) {
|
||||
case FIELD_UINT32: {
|
||||
value.v_uint32 = new std::vector<uint32_t>(*other.value.v_uint32);
|
||||
} break;
|
||||
case FIELD_UINT64: {
|
||||
value.v_uint64 = new std::vector<uint64_t>(*other.value.v_uint64);
|
||||
} break;
|
||||
case FIELD_BYTES: {
|
||||
if (owns_data) {
|
||||
value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>();
|
||||
for (std::pair<uint8_t *, size_t> data_info: *other.value.v_bytes) {
|
||||
uint8_t *new_data = new uint8_t[data_info.second];
|
||||
std::copy_n(data_info.first, data_info.second, new_data);
|
||||
value.v_bytes->push_back({new_data, data_info.second});
|
||||
}
|
||||
} else {
|
||||
value.v_bytes = new std::vector<std::pair<uint8_t *, size_t>>(*other.value.v_bytes);
|
||||
}
|
||||
cached_messages = new std::vector<Message *>();
|
||||
cached_messages->reserve(other.cached_messages->size());
|
||||
for (Message *other_cached_message: *other.cached_messages) {
|
||||
Message *cached_message;
|
||||
if (other_cached_message) {
|
||||
cached_message = new Message(*other_cached_message);
|
||||
} else {
|
||||
cached_message = nullptr;
|
||||
}
|
||||
cached_messages->push_back(cached_message);
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
PP_LOG(ERROR) << "Bad field type when constructing field: " << type;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
Field::~Field() {
|
||||
switch (type) {
|
||||
case FIELD_UINT32:
|
||||
delete value.v_uint32;
|
||||
break;
|
||||
case FIELD_UINT64:
|
||||
delete value.v_uint64;
|
||||
break;
|
||||
case FIELD_BYTES: {
|
||||
if (owns_data)
|
||||
for (std::pair<uint8_t *, size_t> data_info: *value.v_bytes)
|
||||
delete[] data_info.first;
|
||||
delete value.v_bytes;
|
||||
|
||||
for (Message *cached_message: *cached_messages)
|
||||
if (cached_message)
|
||||
delete cached_message;
|
||||
delete cached_messages;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PP_LOG(ERROR) << "Bad field type when destroying field: " << type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Message::Message() : Message(true){};
|
||||
|
||||
Message::Message(bool copy_arrays) : copy_arrays(copy_arrays){};
|
||||
|
||||
Message::Message(const Message &other) : field_map(other.field_map), fields(other.fields), copy_arrays(other.copy_arrays){};
|
||||
|
||||
Message::~Message(){};
|
||||
|
||||
bool Message::ParseFromBytes(uint8_t *bytes, size_t bytes_size) {
|
||||
uint8_t *current = bytes;
|
||||
size_t remaining = bytes_size;
|
||||
while (remaining > 0) {
|
||||
uint8_t wire_type;
|
||||
uint32_t field_number;
|
||||
ReadWireTypeAndFieldNumber(¤t, &remaining, &wire_type, &field_number);
|
||||
switch (wire_type) {
|
||||
case WIRETYPE_VARINT: {
|
||||
Field *field = AddField(field_number, FIELD_UINT64);
|
||||
const uint64_t varint = ReadVarInt(¤t, &remaining);
|
||||
field->value.v_uint64->push_back(varint);
|
||||
break;
|
||||
}
|
||||
case WIRETYPE_64BIT: {
|
||||
Field *field = AddField(field_number, FIELD_UINT64);
|
||||
const uint64_t value = ReadFromBytes<uint64_t>(¤t, &remaining);
|
||||
field->value.v_uint64->push_back(value);
|
||||
break;
|
||||
}
|
||||
case WIRETYPE_LENGTH_DELIMITED: {
|
||||
Field *field = AddField(field_number, FIELD_BYTES);
|
||||
const uint64_t size = ReadVarInt(¤t, &remaining);
|
||||
uint8_t *data;
|
||||
if (copy_arrays) {
|
||||
data = new uint8_t[size];
|
||||
std::copy_n(current, size, data);
|
||||
field->owns_data = true;
|
||||
} else {
|
||||
data = current;
|
||||
field->owns_data = false;
|
||||
}
|
||||
field->value.v_bytes->push_back({data, size});
|
||||
field->cached_messages->push_back(nullptr);
|
||||
current += size;
|
||||
remaining -= size;
|
||||
break;
|
||||
}
|
||||
case WIRETYPE_GROUP_START: {
|
||||
PP_LOG(INFO) << field_number << ": GROUPSTART" << std::endl;
|
||||
PP_LOG(ERROR) << "Unhandled wire type encountered";
|
||||
break;
|
||||
}
|
||||
case WIRETYPE_GROUP_END: {
|
||||
PP_LOG(INFO) << field_number << ": GROUPEND" << std::endl;
|
||||
PP_LOG(ERROR) << "Unhandled wire type encountered";
|
||||
break;
|
||||
}
|
||||
case WIRETYPE_32BIT: {
|
||||
Field *field = AddField(field_number, FIELD_UINT32);
|
||||
const uint32_t value = ReadFromBytes<uint32_t>(¤t, &remaining);
|
||||
field->value.v_uint32->push_back(value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
PP_LOG(ERROR) << "Unknown wire type encountered: " << static_cast<int>(wire_type) << " at offset" << (bytes_size - remaining);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Field *Message::AddField(int32_t number, enum FieldType type) {
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
fields.push_back(Field(type, copy_arrays));
|
||||
field = &fields.back();
|
||||
field_map.insert({number, fields.size() - 1});
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
Field *Message::GetField(int32_t number) {
|
||||
if (field_map.count(number) == 0)
|
||||
return nullptr;
|
||||
return &(fields[field_map[number]]);
|
||||
}
|
||||
|
||||
Field *Message::GetFieldAndCheckType(int32_t number, enum FieldType type) {
|
||||
Field *field = GetField(number);
|
||||
PP_CHECK(field) << "No field for " << number;
|
||||
PP_CHECK(field->type == type) << "For field " << number << " wanted type " << FieldTypeDebugString(type) << " but found " << FieldTypeDebugString(field->type);
|
||||
return field;
|
||||
}
|
||||
|
||||
int32_t Message::GetInt32(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_UINT32);
|
||||
uint32_t first_value = (*(field->value.v_uint32))[0];
|
||||
int32_t zig_zag_decoded = static_cast<int32_t>((first_value >> 1) ^ (-(first_value & 1)));
|
||||
return zig_zag_decoded;
|
||||
}
|
||||
|
||||
int64_t Message::GetInt64(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_UINT64);
|
||||
uint64_t first_value = (*(field->value.v_uint64))[0];
|
||||
int64_t zig_zag_decoded = static_cast<int64_t>((first_value >> 1) ^ (-(first_value & 1)));
|
||||
return zig_zag_decoded;
|
||||
}
|
||||
|
||||
uint32_t Message::GetUInt32(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_UINT32);
|
||||
uint32_t first_value = (*(field->value.v_uint32))[0];
|
||||
return first_value;
|
||||
}
|
||||
|
||||
uint64_t Message::GetUInt64(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_UINT64);
|
||||
uint64_t first_value = (*(field->value.v_uint64))[0];
|
||||
return first_value;
|
||||
}
|
||||
|
||||
int64_t Message::GetInt(int32_t number) {
|
||||
Field *field = GetField(number);
|
||||
PP_CHECK(field) << "No field for " << number;
|
||||
PP_CHECK((field->type == FIELD_UINT32) || (field->type == FIELD_UINT64))
|
||||
<< "For field " << number << " wanted integer type but found " << FieldTypeDebugString(field->type);
|
||||
switch (field->type) {
|
||||
case FIELD_UINT32:
|
||||
return GetInt32(number);
|
||||
break;
|
||||
case FIELD_UINT64:
|
||||
return GetInt64(number);
|
||||
break;
|
||||
default: {
|
||||
// Should never get here.
|
||||
} break;
|
||||
}
|
||||
// Should never get here.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Message::GetBool(int32_t number) {
|
||||
return (GetInt(number) != 0);
|
||||
}
|
||||
|
||||
float Message::GetFloat(int32_t number) {
|
||||
uint32_t int_value = GetUInt32(number);
|
||||
float float_value = *(bit_cast<float *>(&int_value));
|
||||
return float_value;
|
||||
}
|
||||
|
||||
double Message::GetDouble(int32_t number) {
|
||||
uint64_t int_value = GetUInt64(number);
|
||||
return *(bit_cast<double *>(&int_value));
|
||||
}
|
||||
|
||||
std::pair<uint8_t *, size_t> Message::GetBytes(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_BYTES);
|
||||
std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];
|
||||
return first_value;
|
||||
}
|
||||
|
||||
std::string Message::GetString(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_BYTES);
|
||||
std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];
|
||||
std::string result(first_value.first, first_value.first + first_value.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
Message *Message::GetMessage(int32_t number) {
|
||||
Field *field = GetFieldAndCheckType(number, FIELD_BYTES);
|
||||
Message *cached_message = field->cached_messages->at(0);
|
||||
if (!cached_message) {
|
||||
std::pair<uint8_t *, size_t> first_value = (*(field->value.v_bytes))[0];
|
||||
cached_message = new Message(copy_arrays);
|
||||
cached_message->ParseFromBytes(first_value.first, first_value.second);
|
||||
field->cached_messages->at(0) = cached_message;
|
||||
}
|
||||
return cached_message;
|
||||
}
|
||||
|
||||
std::vector<int32_t> Message::GetInt32Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<int32_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
int32_t zig_zag_decoded = static_cast<int32_t>((raw_value >> 1) ^ (-(raw_value & 1)));
|
||||
result.push_back(zig_zag_decoded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<int64_t> Message::GetInt64Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<int64_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
int64_t zig_zag_decoded = static_cast<int64_t>((raw_value >> 1) ^ (-(raw_value & 1)));
|
||||
result.push_back(zig_zag_decoded);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> Message::GetUInt32Array(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<uint32_t> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
result.push_back(static_cast<uint32_t>(raw_value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> Message::GetUInt64Array(int32_t number) {
|
||||
std::vector<uint64_t> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
return result;
|
||||
}
|
||||
if (field->type == FIELD_UINT64) {
|
||||
result.reserve(field->value.v_uint64->size());
|
||||
for (uint64_t value: *field->value.v_uint64) {
|
||||
result.push_back(static_cast<uint64_t>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_UINT32) {
|
||||
result.reserve(field->value.v_uint32->size());
|
||||
for (uint32_t value: *field->value.v_uint32) {
|
||||
result.push_back(static_cast<uint64_t>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {
|
||||
uint8_t *current = data_info.first;
|
||||
size_t remaining = data_info.second;
|
||||
while (remaining > 0) {
|
||||
const uint64_t varint = ReadVarInt(¤t, &remaining);
|
||||
result.push_back(static_cast<int64_t>(varint));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT32, UINT64, or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<bool> Message::GetBoolArray(int32_t number) {
|
||||
std::vector<uint64_t> raw_array = GetUInt64Array(number);
|
||||
std::vector<bool> result;
|
||||
result.reserve(raw_array.size());
|
||||
for (uint64_t raw_value: raw_array) {
|
||||
result.push_back(raw_value != 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<float> Message::GetFloatArray(int32_t number) {
|
||||
std::vector<float> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
return result;
|
||||
}
|
||||
if (field->type == FIELD_UINT32) {
|
||||
result.reserve(field->value.v_uint32->size());
|
||||
for (uint32_t value: *field->value.v_uint32) {
|
||||
result.push_back(bit_cast<float>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {
|
||||
uint8_t *current = data_info.first;
|
||||
size_t remaining = data_info.second;
|
||||
while (remaining > 0) {
|
||||
const uint64_t varint = ReadVarInt(¤t, &remaining);
|
||||
const uint32_t varint32 = static_cast<uint32_t>(varint & 0xffffffff);
|
||||
result.push_back(bit_cast<float>(varint32));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT32 or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<double> Message::GetDoubleArray(int32_t number) {
|
||||
std::vector<double> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
return result;
|
||||
}
|
||||
if (field->type == FIELD_UINT64) {
|
||||
result.reserve(field->value.v_uint64->size());
|
||||
for (uint64_t value: *field->value.v_uint64) {
|
||||
result.push_back(bit_cast<double>(value));
|
||||
}
|
||||
} else if (field->type == FIELD_BYTES) {
|
||||
for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {
|
||||
uint8_t *current = data_info.first;
|
||||
size_t remaining = data_info.second;
|
||||
while (remaining > 0) {
|
||||
const uint64_t varint = ReadVarInt(¤t, &remaining);
|
||||
result.push_back(bit_cast<double>(varint));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type UINT64 or BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint8_t *, size_t>> Message::GetByteArray(int32_t number) {
|
||||
std::vector<std::pair<uint8_t *, size_t>> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field) {
|
||||
return result;
|
||||
}
|
||||
if (field->type == FIELD_BYTES) {
|
||||
result.reserve(field->value.v_bytes->size());
|
||||
for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {
|
||||
result.push_back(data_info);
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> Message::GetStringArray(int32_t number) {
|
||||
std::vector<std::string> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field)
|
||||
return result;
|
||||
if (field->type == FIELD_BYTES) {
|
||||
result.reserve(field->value.v_bytes->size());
|
||||
for (std::pair<uint8_t *, size_t> data_info: *field->value.v_bytes) {
|
||||
result.push_back(std::string(data_info.first, data_info.first + data_info.second));
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Message *> Message::GetMessageArray(int32_t number) {
|
||||
std::vector<Message *> result;
|
||||
Field *field = GetField(number);
|
||||
if (!field)
|
||||
return result;
|
||||
|
||||
if (field->type == FIELD_BYTES) {
|
||||
result.reserve(field->value.v_bytes->size());
|
||||
for (size_t i = 0; i < field->value.v_bytes->size(); ++i) {
|
||||
Message *cached_message = field->cached_messages->at(i);
|
||||
if (!cached_message) {
|
||||
std::pair<uint8_t *, size_t> value = field->value.v_bytes->at(i);
|
||||
cached_message = new Message(copy_arrays);
|
||||
cached_message->ParseFromBytes(value.first, value.second);
|
||||
field->cached_messages->at(i) = cached_message;
|
||||
}
|
||||
result.push_back(cached_message);
|
||||
}
|
||||
} else {
|
||||
PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace picoproto
|
||||
@ -1,208 +0,0 @@
|
||||
/* Copyright 2016 Pete Warden. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==============================================================================*/
|
||||
|
||||
/*
|
||||
See the README for full details, but this module lets you read in protobuf
|
||||
encoded files with a minimal code footprint.
|
||||
|
||||
It doesn't create classes for each kind of message it encounters, it just has a
|
||||
single Message interface that lets you access the members of the protobuf as a
|
||||
key/value store. This loses a lot of the convenience of type-checked classes,
|
||||
but it does mean that very little code is needed to access data from files.
|
||||
|
||||
As a simple example, if you had read a `bytes_size` long file into `bytes` that
|
||||
contained a TensorFlow GraphDef proto:
|
||||
|
||||
Message graph_def;
|
||||
graph_def.ParseFromBytes(bytes, bytes_size);
|
||||
|
||||
You can then access the different fields of the GraphDef using the field
|
||||
numbers assigned in the .proto file:
|
||||
|
||||
std::vector<picoproto::Message*> nodes = graph_def.GetMessageArray(1);
|
||||
|
||||
One big difference between this minimal approach and normal protobufs is that
|
||||
the calling code has to already know the field number and type of any members
|
||||
it's trying to access. Here I know that the `node` field is number 1, and that
|
||||
it should contain a repeated list of NodeDefs. Since they are not primitive
|
||||
types like numbers or strings, they are accessed as an array of Messages.
|
||||
|
||||
Here are the design goals of this module:
|
||||
- Keep the code size tiny (single-digit kilobytes on most platforms).
|
||||
- Minimize memory usage (for example allow in-place references to byte data).
|
||||
- Provide a simple, readable implementation that can be ported easily.
|
||||
- Deserialize all saved protobuf files into a usable representation.
|
||||
- No dependencies other than the standard C++ library.
|
||||
- No build-time support (e.g. protoc) required.
|
||||
|
||||
Here's what it's explicitly not offering:
|
||||
- Providing a readable and transparent way of accessing serialized data.
|
||||
- Saving out data to protobuf format.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_PICOPROTO_H
|
||||
#define INCLUDE_PICOPROTO_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// To keep dependencies minimal, some bare-bones macros to make logging easier.
|
||||
#define PP_LOG(X) PP_LOG_##X
|
||||
#define PP_LOG_INFO std::cerr << __FILE__ << ":" << __LINE__ << " - INFO: "
|
||||
#define PP_LOG_WARN std::cerr << __FILE__ << ":" << __LINE__ << " - WARN: "
|
||||
#define PP_LOG_ERROR std::cerr << __FILE__ << ":" << __LINE__ << " - ERROR: "
|
||||
#define PP_CHECK(X) \
|
||||
if (!(X)) \
|
||||
PP_LOG(ERROR) << "PP_CHECK(" << #X << ") failed. "
|
||||
|
||||
namespace picoproto {
|
||||
|
||||
// These roughly correspond to the wire types used to save data in protobuf
|
||||
// files. The best reference to understand the full format is:
|
||||
// https://developers.google.com/protocol-buffers/docs/encoding
|
||||
// Because we don't know the bit-depth of VarInts, they're always stored as
|
||||
// uint64 values, which is why there's no specific type for them.
|
||||
enum FieldType {
|
||||
FIELD_UNSET,
|
||||
FIELD_UINT32,
|
||||
FIELD_UINT64,
|
||||
FIELD_BYTES,
|
||||
};
|
||||
|
||||
// Gives a readable name for the field type for logging purposes.
|
||||
std::string FieldTypeDebugString(enum FieldType type);
|
||||
|
||||
// Forward declare the main message class, since fields can contain them.
|
||||
class Message;
|
||||
|
||||
// Fields are the building blocks of messages. They contain the values for each
|
||||
// data member, and handle all the allocation and deallocation of storage.
|
||||
// It's unlikely you'll want to access this class directly, since you'll
|
||||
// normally want to use Message below to pull typed values.
|
||||
class Field {
|
||||
public:
|
||||
// You need to specify the type of a Field on creation, so that the right
|
||||
// storage can be set up for the values. You also need to indicate whether the
|
||||
// underlying memory will be around for the lifetime of the message (in which
|
||||
// case no copies are needed) or whether the class should make copies and take
|
||||
// ownership in case the data goes away.
|
||||
Field(FieldType type, bool owns_data);
|
||||
Field(const Field &other);
|
||||
~Field();
|
||||
|
||||
enum FieldType type;
|
||||
// I know, this isn't very OOP, but the simplicity of keeping track of a type
|
||||
// and deciding how to initialize and access the data based on that persuaded
|
||||
// me this was the best approach. The `value` member contains whatever data
|
||||
// the field should be holding.
|
||||
union {
|
||||
std::vector<uint32_t> *v_uint32;
|
||||
std::vector<uint64_t> *v_uint64;
|
||||
std::vector<std::pair<uint8_t *, size_t>> *v_bytes;
|
||||
} value;
|
||||
// One of the drawbacks of not requiring .proto files ahead of time is that I
|
||||
// don't know if a length-delimited field contains raw bytes, strings, or
|
||||
// sub-messages. The only time we know that a field should be interpreted as a
|
||||
// message is when client code requests it in that form. Because parsing can
|
||||
// be costly, here we cache the results of any such calls for subsequent
|
||||
// accesses.
|
||||
std::vector<Message *> *cached_messages;
|
||||
// If this is set, then the object will allocate its own storage for
|
||||
// length-delimited values, and copy from the input stream. If you know the
|
||||
// underlying data will be around for the lifetime of the message, you can
|
||||
// save memory and copies by leaving this as false.
|
||||
bool owns_data;
|
||||
};
|
||||
|
||||
// The main interface for loading and accessing serialized protobuf data.
|
||||
class Message {
|
||||
public:
|
||||
// If you're not sure about the lifetime of any binary data you're reading
|
||||
// from, just call this default constructor.
|
||||
Message();
|
||||
// In the case when you're sure the lifetime of the byte stream you'll be
|
||||
// decoding is longer than the lifetime of the message, you can set
|
||||
// copy_arrays to false. This is especially useful if you have a memory
|
||||
// mapped file to read from containing large binary blobs, since you'll skip
|
||||
// a lot of copying and extra allocation.
|
||||
Message(bool copy_arrays);
|
||||
Message(const Message &other);
|
||||
~Message();
|
||||
|
||||
// Populates fields with all of the data from this stream of bytes.
|
||||
// You can call this repeatedly with new messages, and the results will be
|
||||
// merged together.
|
||||
bool ParseFromBytes(uint8_t *binary, size_t binary_size);
|
||||
|
||||
// These are the accessor functions if you're expecting exactly one value in a
|
||||
// field. As discussed above, the burden is on the client code to know the
|
||||
// field number and type of each member it's trying to access, and so pick the
|
||||
// correct accessor function.
|
||||
// If the field isn't present, this will raise an error, so if it's optional
|
||||
// you should use the array accessors below.
|
||||
int32_t GetInt32(int32_t number);
|
||||
int64_t GetInt64(int32_t number);
|
||||
uint32_t GetUInt32(int32_t number);
|
||||
uint64_t GetUInt64(int32_t number);
|
||||
int64_t GetInt(int32_t number);
|
||||
bool GetBool(int32_t number);
|
||||
float GetFloat(int32_t number);
|
||||
double GetDouble(int32_t number);
|
||||
std::pair<uint8_t *, size_t> GetBytes(int32_t number);
|
||||
std::string GetString(int32_t number);
|
||||
Message *GetMessage(int32_t number);
|
||||
|
||||
// If you're not sure if a value will be present, or if it is repeated, you
|
||||
// should call these array functions. If no such field has been seen, then the
|
||||
// result will be an empty vector, otherwise you'll get back one or more
|
||||
// entries.
|
||||
std::vector<int32_t> GetInt32Array(int32_t number);
|
||||
std::vector<int64_t> GetInt64Array(int32_t number);
|
||||
std::vector<uint32_t> GetUInt32Array(int32_t number);
|
||||
std::vector<uint64_t> GetUInt64Array(int32_t number);
|
||||
std::vector<bool> GetBoolArray(int32_t number);
|
||||
std::vector<float> GetFloatArray(int32_t number);
|
||||
std::vector<double> GetDoubleArray(int32_t number);
|
||||
std::vector<std::pair<uint8_t *, size_t>> GetByteArray(int32_t number);
|
||||
std::vector<std::string> GetStringArray(int32_t number);
|
||||
std::vector<Message *> GetMessageArray(int32_t number);
|
||||
|
||||
// It's unlikely you'll want to access fields directly, but here's an escape
|
||||
// hatch in case you do have to manipulate them more directly.
|
||||
Field *GetField(int32_t number);
|
||||
|
||||
private:
|
||||
// Inserts a new field, updating all the internal data structures.
|
||||
Field *AddField(int32_t number, enum FieldType type);
|
||||
|
||||
Field *GetFieldAndCheckType(int32_t number, enum FieldType type);
|
||||
|
||||
// Maps from a field number to an index in the `fields` vector.
|
||||
std::map<int32_t, size_t> field_map;
|
||||
// The core list of fields that have been parsed.
|
||||
std::vector<Field> fields;
|
||||
bool copy_arrays;
|
||||
};
|
||||
|
||||
} // namespace picoproto
|
||||
|
||||
#endif // INCLUDE_PICOPROTO_H
|
||||
@ -92,15 +92,11 @@ set(PROJECT_SOURCES
|
||||
|
||||
3rdparty/qv2ray/v2/ui/LogHighlighter.cpp
|
||||
3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp
|
||||
3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp
|
||||
3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui
|
||||
|
||||
3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp
|
||||
3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp
|
||||
|
||||
rpc/gRPC.cpp
|
||||
|
||||
db/Database.cpp
|
||||
|
||||
@ -453,14 +453,6 @@ namespace NekoGui {
|
||||
inboundObj["sniff"] = true;
|
||||
inboundObj["sniff_override_destination"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION;
|
||||
}
|
||||
if (dataStore->inbound_auth->NeedAuth()) {
|
||||
inboundObj["users"] = QJsonArray{
|
||||
QJsonObject{
|
||||
{"username", dataStore->inbound_auth->username},
|
||||
{"password", dataStore->inbound_auth->password},
|
||||
},
|
||||
};
|
||||
}
|
||||
inboundObj["domain_strategy"] = dataStore->routing->domain_strategy;
|
||||
status->inbounds += inboundObj;
|
||||
}
|
||||
|
||||
@ -121,80 +121,59 @@ namespace NekoGui_fmt {
|
||||
}
|
||||
|
||||
QString VMessBean::ToShareLink() {
|
||||
if (NekoGui::dataStore->old_share_link_format) {
|
||||
// v2rayN format
|
||||
QJsonObject N{
|
||||
{"v", "2"},
|
||||
{"ps", name},
|
||||
{"add", serverAddress},
|
||||
{"port", Int2String(serverPort)},
|
||||
{"id", uuid},
|
||||
{"aid", Int2String(aid)},
|
||||
{"net", stream->network},
|
||||
{"host", stream->host},
|
||||
{"path", stream->path},
|
||||
{"type", stream->header_type},
|
||||
{"scy", security},
|
||||
{"tls", stream->security == "tls" ? "tls" : ""},
|
||||
{"sni", stream->sni},
|
||||
};
|
||||
return "vmess://" + QJsonObject2QString(N, true).toUtf8().toBase64();
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme("vmess");
|
||||
url.setUserName(uuid);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
|
||||
query.addQueryItem("encryption", security);
|
||||
|
||||
// security
|
||||
auto security = stream->security;
|
||||
if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality";
|
||||
query.addQueryItem("security", security);
|
||||
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1");
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
query.addQueryItem("fp", NekoGui::dataStore->utlsFingerprint);
|
||||
} else {
|
||||
// ducksoft format
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme("vmess");
|
||||
url.setUserName(uuid);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(name);
|
||||
|
||||
query.addQueryItem("encryption", security);
|
||||
|
||||
// security
|
||||
auto security = stream->security;
|
||||
if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality";
|
||||
query.addQueryItem("security", security);
|
||||
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1");
|
||||
if (stream->utlsFingerprint.isEmpty()) {
|
||||
query.addQueryItem("fp", NekoGui::dataStore->utlsFingerprint);
|
||||
} else {
|
||||
query.addQueryItem("fp", stream->utlsFingerprint);
|
||||
}
|
||||
|
||||
if (security == "reality") {
|
||||
query.addQueryItem("pbk", stream->reality_pbk);
|
||||
if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid);
|
||||
if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx);
|
||||
}
|
||||
|
||||
// type
|
||||
query.addQueryItem("type", stream->network);
|
||||
|
||||
if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host);
|
||||
} else if (stream->network == "grpc") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path);
|
||||
} else if (stream->network == "tcp") {
|
||||
if (stream->header_type == "http") {
|
||||
query.addQueryItem("headerType", "http");
|
||||
query.addQueryItem("host", stream->host);
|
||||
}
|
||||
}
|
||||
|
||||
// mux
|
||||
if (mux_state == 1) {
|
||||
query.addQueryItem("mux", "true");
|
||||
} else if (mux_state == 2) {
|
||||
query.addQueryItem("mux", "false");
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
query.addQueryItem("fp", stream->utlsFingerprint);
|
||||
}
|
||||
|
||||
if (security == "reality") {
|
||||
query.addQueryItem("pbk", stream->reality_pbk);
|
||||
if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid);
|
||||
if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx);
|
||||
}
|
||||
|
||||
// type
|
||||
query.addQueryItem("type", stream->network);
|
||||
|
||||
if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host);
|
||||
} else if (stream->network == "grpc") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path);
|
||||
} else if (stream->network == "tcp") {
|
||||
if (stream->header_type == "http") {
|
||||
query.addQueryItem("headerType", "http");
|
||||
query.addQueryItem("host", stream->host);
|
||||
}
|
||||
}
|
||||
|
||||
// mux
|
||||
if (mux_state == 1) {
|
||||
query.addQueryItem("mux", "true");
|
||||
} else if (mux_state == 2) {
|
||||
query.addQueryItem("mux", "false");
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QString NaiveBean::ToShareLink() {
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
#include <QByteArray>
|
||||
#include <QMetaEnum>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QApplication>
|
||||
#include "cpr/cpr.h"
|
||||
|
||||
#include "main/NekoGui.hpp"
|
||||
@ -11,14 +16,10 @@ namespace NekoGui_network {
|
||||
|
||||
NekoHTTPResponse NetworkRequestHelper::HttpGet(const QString &url) {
|
||||
cpr::Session session;
|
||||
if (NekoGui::dataStore->sub_use_proxy) {
|
||||
if (NekoGui::dataStore->sub_use_proxy || NekoGui::dataStore->spmode_system_proxy) {
|
||||
session.SetProxies({{"http", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()},
|
||||
{"https", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()}});
|
||||
if (NekoGui::dataStore->inbound_auth->NeedAuth()) {
|
||||
session.SetProxyAuth(cpr::ProxyAuthentication{{"http",
|
||||
cpr::EncodedAuthentication{NekoGui::dataStore->inbound_auth->username.toStdString(), NekoGui::dataStore->inbound_auth->password.toStdString()}}});
|
||||
}
|
||||
if (NekoGui::dataStore->started_id < 0) {
|
||||
if (NekoGui::dataStore->started_id < 0 && NekoGui::dataStore->sub_use_proxy) {
|
||||
return NekoHTTPResponse{QObject::tr("Request with proxy but no profile started.")};
|
||||
}
|
||||
}
|
||||
@ -46,4 +47,59 @@ namespace NekoGui_network {
|
||||
return "";
|
||||
}
|
||||
|
||||
QString NetworkRequestHelper::GetLatestDownloadURL(const QString &url, const QString &assetName, bool* success) {
|
||||
cpr::Session session;
|
||||
session.SetUrl(cpr::Url{url.toStdString()});
|
||||
session.SetTimeout(3000);
|
||||
if (NekoGui::dataStore->spmode_system_proxy) {
|
||||
session.SetProxies({{"http", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()},
|
||||
{"https", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()}});
|
||||
}
|
||||
cpr::Response r = session.Get();
|
||||
if (r.status_code != 200) {
|
||||
*success = false;
|
||||
return {r.status_line.c_str()};
|
||||
}
|
||||
auto respObj = QString2QJsonObject(QString(r.text.c_str()));
|
||||
auto assets = respObj["assets"].toArray();
|
||||
for (const auto &asset: assets) {
|
||||
auto assetObj = asset.toObject();
|
||||
if (assetObj["name"] == assetName) {
|
||||
*success = true;
|
||||
return assetObj["browser_download_url"].toString();
|
||||
}
|
||||
}
|
||||
|
||||
*success = false;
|
||||
return "not found";
|
||||
}
|
||||
|
||||
QString NetworkRequestHelper::DownloadGeoAsset(const QString &url, const QString &fileName) {
|
||||
cpr::Session session;
|
||||
session.SetUrl(cpr::Url{url.toStdString()});
|
||||
session.SetTimeout(3000);
|
||||
if (NekoGui::dataStore->spmode_system_proxy) {
|
||||
session.SetProxies({{"http", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()},
|
||||
{"https", "127.0.0.1:" + QString(Int2String(NekoGui::dataStore->inbound_socks_port)).toStdString()}});
|
||||
}
|
||||
auto filePath = qApp->applicationDirPath()+ "/" + fileName;
|
||||
std::ofstream fout;
|
||||
fout.open(QString(filePath + ".1").toStdString(), std::ios::trunc | std::ios::out | std::ios::binary);
|
||||
auto r = session.Download(fout);
|
||||
fout.close();
|
||||
auto tmpFile = QFile(filePath + ".1");
|
||||
if (r.status_code != 200) {
|
||||
tmpFile.remove();
|
||||
if (r.status_code == 0) {
|
||||
return "Please check the URL and your network Connectivity";
|
||||
}
|
||||
return r.status_line.c_str();
|
||||
}
|
||||
QFile(filePath).remove();
|
||||
if (!tmpFile.rename(filePath)) {
|
||||
return tmpFile.errorString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace NekoGui_network
|
||||
|
||||
@ -25,6 +25,10 @@ namespace NekoGui_network {
|
||||
static NekoHTTPResponse HttpGet(const QString &url);
|
||||
|
||||
static QString GetHeader(const QList<QPair<QByteArray, QByteArray>> &header, const QString &name);
|
||||
|
||||
static QString GetLatestDownloadURL(const QString &url, const QString &assetName, bool* success);
|
||||
|
||||
static QString DownloadGeoAsset(const QString &url, const QString &fileName);
|
||||
};
|
||||
} // namespace NekoGui_network
|
||||
|
||||
|
||||
@ -240,16 +240,12 @@ namespace NekoGui {
|
||||
|
||||
DataStore::DataStore() : JsonStore() {
|
||||
_add(new configItem("extraCore", dynamic_cast<JsonStore *>(extraCore), itemType::jsonStore));
|
||||
_add(new configItem("inbound_auth", dynamic_cast<JsonStore *>(inbound_auth), itemType::jsonStore));
|
||||
|
||||
_add(new configItem("user_agent2", &user_agent, itemType::string));
|
||||
_add(new configItem("test_url", &test_latency_url, itemType::string));
|
||||
_add(new configItem("test_url_dl", &test_download_url, itemType::string));
|
||||
_add(new configItem("test_dl_timeout", &test_download_timeout, itemType::integer));
|
||||
_add(new configItem("current_group", ¤t_group, itemType::integer));
|
||||
_add(new configItem("inbound_address", &inbound_address, itemType::string));
|
||||
_add(new configItem("inbound_socks_port", &inbound_socks_port, itemType::integer));
|
||||
_add(new configItem("inbound_http_port", &inbound_http_port, itemType::integer));
|
||||
_add(new configItem("log_level", &log_level, itemType::string));
|
||||
_add(new configItem("mux_protocol", &mux_protocol, itemType::string));
|
||||
_add(new configItem("mux_concurrency", &mux_concurrency, itemType::integer));
|
||||
@ -260,7 +256,6 @@ namespace NekoGui {
|
||||
_add(new configItem("theme", &theme, itemType::string));
|
||||
_add(new configItem("custom_inbound", &custom_inbound, itemType::string));
|
||||
_add(new configItem("custom_route", &custom_route_global, itemType::string));
|
||||
_add(new configItem("v2ray_asset_dir", &v2ray_asset_dir, itemType::string));
|
||||
_add(new configItem("sub_use_proxy", &sub_use_proxy, itemType::boolean));
|
||||
_add(new configItem("remember_id", &remember_id, itemType::integer));
|
||||
_add(new configItem("remember_enable", &remember_enable, itemType::boolean));
|
||||
@ -281,8 +276,6 @@ namespace NekoGui {
|
||||
_add(new configItem("vpn_ipv6", &vpn_ipv6, itemType::boolean));
|
||||
_add(new configItem("vpn_hide_console", &vpn_hide_console, itemType::boolean));
|
||||
_add(new configItem("vpn_strict_route", &vpn_strict_route, itemType::boolean));
|
||||
_add(new configItem("check_include_pre", &check_include_pre, itemType::boolean));
|
||||
_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));
|
||||
@ -300,6 +293,8 @@ namespace NekoGui {
|
||||
_add(new configItem("ntp_server_address", &ntp_server_address, itemType::string));
|
||||
_add(new configItem("ntp_server_port", &ntp_server_port, itemType::integer));
|
||||
_add(new configItem("ntp_interval", &ntp_interval, itemType::string));
|
||||
_add(new configItem("geoip_download_url", &geoip_download_url, itemType::string));
|
||||
_add(new configItem("geosite_download_url", &geosite_download_url, itemType::string));
|
||||
}
|
||||
|
||||
void DataStore::UpdateStartedId(int id) {
|
||||
@ -373,25 +368,12 @@ namespace NekoGui {
|
||||
core_map = QJsonObject2QString(obj, true);
|
||||
}
|
||||
|
||||
InboundAuthorization::InboundAuthorization() : JsonStore() {
|
||||
_add(new configItem("user", &this->username, itemType::string));
|
||||
_add(new configItem("pass", &this->password, itemType::string));
|
||||
}
|
||||
|
||||
bool InboundAuthorization::NeedAuth() const {
|
||||
return !username.trimmed().isEmpty() && !password.trimmed().isEmpty();
|
||||
}
|
||||
|
||||
// System Utils
|
||||
|
||||
QString FindCoreAsset(const QString &name) {
|
||||
QStringList search{NekoGui::dataStore->v2ray_asset_dir};
|
||||
search << QApplication::applicationDirPath();
|
||||
QStringList search{QApplication::applicationDirPath()};
|
||||
search << "/usr/share/sing-geoip";
|
||||
search << "/usr/share/sing-geosite";
|
||||
search << "/usr/share/xray";
|
||||
search << "/usr/local/share/xray";
|
||||
search << "/opt/xray";
|
||||
search << "/usr/share/v2ray";
|
||||
search << "/usr/local/share/v2ray";
|
||||
search << "/opt/v2ray";
|
||||
|
||||
@ -39,16 +39,6 @@ namespace NekoGui {
|
||||
void Delete(const QString &id);
|
||||
};
|
||||
|
||||
class InboundAuthorization : public JsonStore {
|
||||
public:
|
||||
QString username;
|
||||
QString password;
|
||||
|
||||
InboundAuthorization();
|
||||
|
||||
[[nodiscard]] bool NeedAuth() const;
|
||||
};
|
||||
|
||||
class DataStore : public JsonStore {
|
||||
public:
|
||||
// Running
|
||||
@ -84,10 +74,7 @@ namespace NekoGui {
|
||||
// Misc
|
||||
QString log_level = "warning";
|
||||
QString test_latency_url = "http://cp.cloudflare.com/";
|
||||
QString test_download_url = "http://cachefly.cachefly.net/10mb.test";
|
||||
int test_download_timeout = 30;
|
||||
int test_concurrent = 5;
|
||||
bool old_share_link_format = true;
|
||||
int traffic_loop_interval = 1000;
|
||||
bool disable_traffic_stats = false;
|
||||
int current_group = 0; // group id
|
||||
@ -96,11 +83,8 @@ namespace NekoGui {
|
||||
int mux_concurrency = 8;
|
||||
bool mux_default_on = false;
|
||||
QString theme = "0";
|
||||
QString v2ray_asset_dir = "";
|
||||
int language = 0;
|
||||
QString mw_size = "";
|
||||
bool check_include_pre = true;
|
||||
QString system_proxy_format = "";
|
||||
QStringList log_ignore = {};
|
||||
bool start_minimal = false;
|
||||
int max_log_line = 200;
|
||||
@ -113,6 +97,10 @@ namespace NekoGui {
|
||||
bool sub_insecure = false;
|
||||
int sub_auto_update = -30;
|
||||
|
||||
// Assets
|
||||
QString geoip_download_url = "";
|
||||
QString geosite_download_url = "";
|
||||
|
||||
// Security
|
||||
bool skip_cert = false;
|
||||
QString utlsFingerprint = "";
|
||||
@ -124,9 +112,7 @@ namespace NekoGui {
|
||||
|
||||
// Socks & HTTP Inbound
|
||||
QString inbound_address = "127.0.0.1";
|
||||
int inbound_socks_port = 2080; // or Mixed
|
||||
int inbound_http_port = 2081;
|
||||
InboundAuthorization *inbound_auth = new InboundAuthorization;
|
||||
int inbound_socks_port = 2080; // Mixed, actually
|
||||
QString custom_inbound = "{\"inbounds\": []}";
|
||||
|
||||
// Routing
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "ui/Icon.hpp"
|
||||
#include "main/GuiUtils.hpp"
|
||||
#include "main/NekoGui.hpp"
|
||||
#include "main/HTTPRequestHelper.hpp"
|
||||
|
||||
#include <QStyleFactory>
|
||||
#include <QFileDialog>
|
||||
@ -58,48 +59,25 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
||||
ADD_ASTERISK(this);
|
||||
|
||||
// Common
|
||||
|
||||
ui->groupBox_http->hide();
|
||||
ui->inbound_socks_port_l->setText(ui->inbound_socks_port_l->text().replace("Socks", "Mixed (SOCKS+HTTP)"));
|
||||
ui->log_level->addItems(QString("trace debug info warn error fatal panic").split(" "));
|
||||
ui->mux_protocol->addItems({"h2mux", "smux", "yamux"});
|
||||
ui->disable_stats->setChecked(NekoGui::dataStore->disable_traffic_stats);
|
||||
|
||||
refresh_auth();
|
||||
|
||||
D_LOAD_STRING(inbound_address)
|
||||
D_LOAD_COMBO_STRING(log_level)
|
||||
CACHE.custom_inbound = NekoGui::dataStore->custom_inbound;
|
||||
D_LOAD_INT(inbound_socks_port)
|
||||
D_LOAD_INT_ENABLE(inbound_http_port, http_enable)
|
||||
D_LOAD_INT(test_concurrent)
|
||||
D_LOAD_INT(test_download_timeout)
|
||||
D_LOAD_STRING(test_latency_url)
|
||||
D_LOAD_STRING(test_download_url)
|
||||
D_LOAD_BOOL(old_share_link_format)
|
||||
|
||||
connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] {
|
||||
C_EDIT_JSON_ALLOW_EMPTY(custom_inbound)
|
||||
});
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
connect(ui->sys_proxy_format, &QPushButton::clicked, this, [=] {
|
||||
bool ok;
|
||||
auto str = QInputDialog::getItem(this, ui->sys_proxy_format->text() + " (Windows)",
|
||||
tr("Advanced system proxy settings. Please select a format."),
|
||||
Preset::Windows::system_proxy_format,
|
||||
Preset::Windows::system_proxy_format.indexOf(NekoGui::dataStore->system_proxy_format),
|
||||
false, &ok);
|
||||
if (ok) NekoGui::dataStore->system_proxy_format = str;
|
||||
});
|
||||
#else
|
||||
ui->sys_proxy_format->hide();
|
||||
#endif
|
||||
|
||||
// Style
|
||||
ui->connection_statistics_box->setDisabled(true);
|
||||
//
|
||||
D_LOAD_BOOL(check_include_pre)
|
||||
D_LOAD_BOOL(start_minimal)
|
||||
D_LOAD_INT(max_log_line)
|
||||
//
|
||||
@ -149,9 +127,47 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
||||
D_LOAD_INT_ENABLE(sub_auto_update, sub_auto_update_enable)
|
||||
|
||||
// Core
|
||||
|
||||
ui->groupBox_core->setTitle(software_core_name);
|
||||
ui->core_v2ray_asset->setText(NekoGui::dataStore->v2ray_asset_dir);
|
||||
|
||||
// Assets
|
||||
ui->geoip_url->setText(NekoGui::dataStore->geoip_download_url);
|
||||
ui->geosite_url->setText(NekoGui::dataStore->geosite_download_url);
|
||||
connect(ui->geoip_auto_btn, &QPushButton::clicked, this, [=](){
|
||||
bool success;
|
||||
auto resp = NetworkRequestHelper::GetLatestDownloadURL("https://api.github.com/repos/SagerNet/sing-geoip/releases/latest", "geoip.db", &success);
|
||||
if (!success) {
|
||||
runOnUiThread([=](){
|
||||
MessageBoxWarning("Error", resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
ui->geoip_url->setText(resp);
|
||||
});
|
||||
connect(ui->geosite_auto_btn, &QPushButton::clicked, this, [=](){
|
||||
bool success;
|
||||
auto resp = NetworkRequestHelper::GetLatestDownloadURL("https://api.github.com/repos/SagerNet/sing-geosite/releases/latest", "geosite.db", &success);
|
||||
if (!success) {
|
||||
runOnUiThread([=](){
|
||||
MessageBoxWarning("Error", resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
ui->geosite_url->setText(resp);
|
||||
});
|
||||
connect(ui->download_geo_btn, &QPushButton::clicked, this, [=]() {
|
||||
MW_dialog_message(Dialog_DialogBasicSettings, "DownloadAssets;"+ui->geoip_url->text()+";"+ui->geosite_url->text());
|
||||
});
|
||||
connect(ui->remove_srs_btn, &QPushButton::clicked, this, [=](){
|
||||
auto rsDir = QDir(RULE_SETS_DIR);
|
||||
auto entries = rsDir.entryList(QDir::Files);
|
||||
for (const auto &item: entries) {
|
||||
if (!QFile(RULE_SETS_DIR + "/" + item).remove()) {
|
||||
MW_show_log("Failed to remove " + item + ", stop the core then try again");
|
||||
}
|
||||
}
|
||||
MW_show_log("Removed all rule-set files");
|
||||
});
|
||||
|
||||
//
|
||||
CACHE.extraCore = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map);
|
||||
if (!CACHE.extraCore.contains("naive")) CACHE.extraCore.insert("naive", "");
|
||||
@ -164,16 +180,6 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)
|
||||
extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s));
|
||||
}
|
||||
//
|
||||
connect(ui->core_v2ray_asset, &QLineEdit::textChanged, this, [=] {
|
||||
CACHE.needRestart = true;
|
||||
});
|
||||
connect(ui->core_v2ray_asset_pick, &QPushButton::clicked, this, [=] {
|
||||
auto fn = QFileDialog::getExistingDirectory(this, tr("Select"), QDir::currentPath(),
|
||||
QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly);
|
||||
if (!fn.isEmpty()) {
|
||||
ui->core_v2ray_asset->setText(fn);
|
||||
}
|
||||
});
|
||||
connect(ui->extra_core_add, &QPushButton::clicked, this, [=] {
|
||||
bool ok;
|
||||
auto s = QInputDialog::getText(nullptr, tr("Add"),
|
||||
@ -241,17 +247,12 @@ void DialogBasicSettings::accept() {
|
||||
D_SAVE_COMBO_STRING(log_level)
|
||||
NekoGui::dataStore->custom_inbound = CACHE.custom_inbound;
|
||||
D_SAVE_INT(inbound_socks_port)
|
||||
D_SAVE_INT_ENABLE(inbound_http_port, http_enable)
|
||||
D_SAVE_INT(test_concurrent)
|
||||
D_SAVE_INT(test_download_timeout)
|
||||
D_SAVE_STRING(test_latency_url)
|
||||
D_SAVE_STRING(test_download_url)
|
||||
D_SAVE_BOOL(old_share_link_format)
|
||||
|
||||
// Style
|
||||
|
||||
NekoGui::dataStore->language = ui->language->currentIndex();
|
||||
D_SAVE_BOOL(check_include_pre)
|
||||
D_SAVE_BOOL(start_minimal)
|
||||
D_SAVE_INT(max_log_line)
|
||||
|
||||
@ -288,11 +289,13 @@ void DialogBasicSettings::accept() {
|
||||
D_SAVE_INT_ENABLE(sub_auto_update, sub_auto_update_enable)
|
||||
|
||||
// Core
|
||||
|
||||
NekoGui::dataStore->v2ray_asset_dir = ui->core_v2ray_asset->text();
|
||||
NekoGui::dataStore->extraCore->core_map = QJsonObject2QString(CACHE.extraCore, true);
|
||||
NekoGui::dataStore->disable_traffic_stats = ui->disable_stats->isChecked();
|
||||
|
||||
// Assets
|
||||
NekoGui::dataStore->geoip_download_url = ui->geoip_url->text();
|
||||
NekoGui::dataStore->geosite_download_url = ui->geosite_url->text();
|
||||
|
||||
// Mux
|
||||
D_SAVE_INT(mux_concurrency)
|
||||
D_SAVE_COMBO_STRING(mux_protocol)
|
||||
@ -316,17 +319,6 @@ void DialogBasicSettings::accept() {
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
// slots
|
||||
|
||||
void DialogBasicSettings::refresh_auth() {
|
||||
ui->inbound_auth->setText({});
|
||||
if (NekoGui::dataStore->inbound_auth->NeedAuth()) {
|
||||
ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-outline"));
|
||||
} else {
|
||||
ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-open-outline"));
|
||||
}
|
||||
}
|
||||
|
||||
void DialogBasicSettings::on_set_custom_icon_clicked() {
|
||||
auto title = ui->set_custom_icon->text();
|
||||
QString user_icon_path = "./" + software_name.toLower() + ".png";
|
||||
@ -350,40 +342,6 @@ void DialogBasicSettings::on_set_custom_icon_clicked() {
|
||||
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateIcon");
|
||||
}
|
||||
|
||||
void DialogBasicSettings::on_inbound_auth_clicked() {
|
||||
auto w = new QDialog(this);
|
||||
w->setWindowTitle(tr("Inbound Auth"));
|
||||
auto layout = new QGridLayout;
|
||||
w->setLayout(layout);
|
||||
//
|
||||
auto user_l = new QLabel(tr("Username"));
|
||||
auto pass_l = new QLabel(tr("Password"));
|
||||
auto user = new MyLineEdit;
|
||||
auto pass = new MyLineEdit;
|
||||
user->setText(NekoGui::dataStore->inbound_auth->username);
|
||||
pass->setText(NekoGui::dataStore->inbound_auth->password);
|
||||
//
|
||||
layout->addWidget(user_l, 0, 0);
|
||||
layout->addWidget(user, 0, 1);
|
||||
layout->addWidget(pass_l, 1, 0);
|
||||
layout->addWidget(pass, 1, 1);
|
||||
auto box = new QDialogButtonBox;
|
||||
box->setOrientation(Qt::Horizontal);
|
||||
box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
connect(box, &QDialogButtonBox::accepted, w, [=] {
|
||||
NekoGui::dataStore->inbound_auth->username = user->text();
|
||||
NekoGui::dataStore->inbound_auth->password = pass->text();
|
||||
MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");
|
||||
w->accept();
|
||||
});
|
||||
connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject);
|
||||
layout->addWidget(box, 2, 1);
|
||||
//
|
||||
w->exec();
|
||||
w->deleteLater();
|
||||
refresh_auth();
|
||||
}
|
||||
|
||||
void DialogBasicSettings::on_core_settings_clicked() {
|
||||
auto w = new QDialog(this);
|
||||
w->setWindowTitle(software_core_name + " Core Options");
|
||||
@ -449,5 +407,4 @@ void DialogBasicSettings::on_core_settings_clicked() {
|
||||
ADD_ASTERISK(w)
|
||||
w->exec();
|
||||
w->deleteLater();
|
||||
refresh_auth();
|
||||
}
|
||||
|
||||
@ -31,12 +31,8 @@ private:
|
||||
|
||||
private slots:
|
||||
|
||||
void refresh_auth();
|
||||
|
||||
void on_set_custom_icon_clicked();
|
||||
|
||||
void on_inbound_auth_clicked();
|
||||
|
||||
void on_core_settings_clicked();
|
||||
};
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<item row="0" column="3">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
@ -61,47 +61,16 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="inbound_address"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="inbound_auth">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_custom_inbound">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Custom Inbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="custom_inbound_edit">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hlayout_l2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="horizontalGroupBox_2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_13">
|
||||
<item>
|
||||
<widget class="QLabel" name="inbound_socks_port_l">
|
||||
<property name="text">
|
||||
<string>Socks Listen Port</string>
|
||||
<string>Listen Port</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -118,30 +87,24 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="hlayout_l2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_http">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_16">
|
||||
<widget class="QGroupBox" name="groupbox_custom_inbound">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>HTTP Listen Port</string>
|
||||
<string>Custom Inbound</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="inbound_http_port">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="http_enable">
|
||||
<widget class="QPushButton" name="custom_inbound_edit">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -176,69 +139,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="horizontalGroupBox">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12" stretch="0,8,1,1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_19">
|
||||
<property name="text">
|
||||
<string>Download Test URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MyLineEdit" name="test_download_url"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Timeout (s)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="test_download_timeout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="horizontalGroupBox1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_18">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="check_include_pre">
|
||||
<property name="text">
|
||||
<string>Include Pre-release when checking update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="sys_proxy_format_vline">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="old_share_link_format">
|
||||
<property name="toolTip">
|
||||
<string>Share VMess Link with v2rayN Format</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Old Share Link Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sys_proxy_format">
|
||||
<property name="text">
|
||||
<string>System proxy format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="horizontalWidget_1" native="true">
|
||||
<property name="sizePolicy">
|
||||
@ -587,39 +487,7 @@
|
||||
<item>
|
||||
<widget class="QWidget" name="assest_group" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Asset Location</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="0" column="0">
|
||||
<widget class="MyLineEdit" name="core_v2ray_asset">
|
||||
<property name="placeholderText">
|
||||
<string>Default: dir of "nekoray"</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="core_v2ray_asset_pick">
|
||||
<property name="text">
|
||||
<string>Select</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string notr="true">Loglevel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_20">
|
||||
<item>
|
||||
<widget class="QComboBox" name="log_level">
|
||||
@ -649,14 +517,21 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Multiplex (mux)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string notr="true">Loglevel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="mux_protocol"/>
|
||||
@ -781,6 +656,93 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Assets</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Geo Assets and Rule-sets</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove_srs_btn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Remove the currently generated rule-sets so that they can be regenerated</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove Generated Rule-sets</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<item>
|
||||
<widget class="QLabel" name="geoip_url_l">
|
||||
<property name="text">
|
||||
<string>GeoIP URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="geoip_url"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="geoip_auto_btn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Sets the URL to the latest sing-geoip release URL</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Sing-Geoip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_14">
|
||||
<item>
|
||||
<widget class="QLabel" name="geosite_url_l">
|
||||
<property name="text">
|
||||
<string>GeoSite URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="geosite_url"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="geosite_auto_btn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Sets the URL to the latest sing-geosite release URL</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Sing-Geosite</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="download_geo_btn">
|
||||
<property name="text">
|
||||
<string>Download and Replace Geo files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_6">
|
||||
<attribute name="title">
|
||||
<string>Extra Core</string>
|
||||
@ -799,8 +761,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>686</width>
|
||||
<height>377</height>
|
||||
<width>680</width>
|
||||
<height>355</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
#include "db/Database.hpp"
|
||||
|
||||
#include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp"
|
||||
#include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp"
|
||||
#include "main/GuiUtils.hpp"
|
||||
#include "fmt/Preset.hpp"
|
||||
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStyleHints>
|
||||
#include <main/HTTPRequestHelper.hpp>
|
||||
|
||||
void UI_InitMainWindow() {
|
||||
mainwindow = new MainWindow;
|
||||
@ -531,6 +532,12 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info)
|
||||
on_menu_exit_triggered();
|
||||
}
|
||||
}
|
||||
if (info.contains("DownloadAssets")) {
|
||||
auto splitted = info.split(";");
|
||||
runOnNewThread([=](){
|
||||
DownloadAssets(splitted[1], splitted[2]);
|
||||
});
|
||||
}
|
||||
//
|
||||
if (info == "RestartProgram") {
|
||||
this->exit_reason = 2;
|
||||
@ -803,13 +810,8 @@ void MainWindow::refresh_status(const QString &traffic_update) {
|
||||
ui->label_running->setText(txt);
|
||||
}
|
||||
//
|
||||
auto display_http = tr("None");
|
||||
if (IsValidPort(NekoGui::dataStore->inbound_http_port)) {
|
||||
display_http = DisplayAddress(NekoGui::dataStore->inbound_address, NekoGui::dataStore->inbound_http_port);
|
||||
}
|
||||
auto display_socks = DisplayAddress(NekoGui::dataStore->inbound_address, NekoGui::dataStore->inbound_socks_port);
|
||||
auto inbound_txt = QString("Socks: %1\nHTTP: %2").arg(display_socks, display_http);
|
||||
inbound_txt = QString("Mixed: %1").arg(display_socks);
|
||||
auto inbound_txt = QString("Mixed: %1").arg(display_socks);
|
||||
ui->label_inbound->setText(inbound_txt);
|
||||
//
|
||||
ui->checkBox_VPN->setChecked(NekoGui::dataStore->spmode_vpn);
|
||||
@ -1673,3 +1675,35 @@ bool MainWindow::StopVPNProcess(bool unconditional) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::DownloadAssets(const QString &geoipUrl, const QString &geositeUrl) {
|
||||
if (!mu_download_assets.tryLock()) {
|
||||
runOnUiThread([=](){
|
||||
MessageBoxWarning("Cannot start", "Last download request has not finished yet");
|
||||
});
|
||||
return;
|
||||
}
|
||||
MW_show_log("Start downloading...");
|
||||
QString errors;
|
||||
if (!geoipUrl.isEmpty()) {
|
||||
auto resp = NetworkRequestHelper::DownloadGeoAsset(geoipUrl, "geoip.db");
|
||||
if (!resp.isEmpty()) {
|
||||
MW_show_log(QString("Failed to download geoip: %1").arg(resp));
|
||||
errors += "geoip: " + resp;
|
||||
}
|
||||
}
|
||||
if (!geositeUrl.isEmpty()) {
|
||||
auto resp = NetworkRequestHelper::DownloadGeoAsset(geositeUrl, "geosite.db");
|
||||
if (!resp.isEmpty()) {
|
||||
MW_show_log(QString("Failed to download geosite: %1").arg(resp));
|
||||
errors += "\ngeosite: " + resp;
|
||||
}
|
||||
}
|
||||
mu_download_assets.unlock();
|
||||
if (!errors.isEmpty()) {
|
||||
runOnUiThread([=](){
|
||||
MessageBoxWarning("Failed to download geo assets", errors);
|
||||
});
|
||||
}
|
||||
MW_show_log("Geo Asset update completed!");
|
||||
}
|
||||
@ -68,6 +68,8 @@ public:
|
||||
|
||||
bool StopVPNProcess(bool unconditional = false);
|
||||
|
||||
void DownloadAssets(const QString &geoipUrl, const QString &geositeUrl);
|
||||
|
||||
signals:
|
||||
|
||||
void profile_selected(int id);
|
||||
@ -159,6 +161,8 @@ private:
|
||||
QMutex mu_exit;
|
||||
QSemaphore sem_stopped;
|
||||
int exit_reason = 0;
|
||||
//
|
||||
QMutex mu_download_assets;
|
||||
|
||||
QList<std::shared_ptr<NekoGui::ProxyEntity>> get_now_selected_list();
|
||||
|
||||
|
||||
@ -491,7 +491,6 @@ void MainWindow::CheckUpdate() {
|
||||
bool ok;
|
||||
libcore::UpdateReq request;
|
||||
request.set_action(libcore::UpdateAction::Check);
|
||||
request.set_check_pre_release(NekoGui::dataStore->check_include_pre);
|
||||
auto response = NekoGui_rpc::defaultClient->Update(&ok, request);
|
||||
if (!ok) return;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user