mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-30 02:48:11 +03:00
Add support for Xray-core logging in Pythonw mode
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+64
-27
@@ -15,7 +15,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Core.Core import XrayCore, Hysteria1, Hysteria2
|
||||
from Furious.Core.Core import XrayCore, Hysteria1, Hysteria2, StdoutRedirectHelper
|
||||
from Furious.Core.TorRelay import TorRelay
|
||||
from Furious.Core.Tun2socks import Tun2socks
|
||||
from Furious.Core.Intellisense import Intellisense
|
||||
@@ -46,6 +46,7 @@ from Furious.Utility.Utility import (
|
||||
isValidIPAddress,
|
||||
isAdministrator,
|
||||
isVPNMode,
|
||||
isPythonw,
|
||||
)
|
||||
from Furious.Utility.Translator import gettext as _
|
||||
from Furious.Utility.Proxy import Proxy
|
||||
@@ -60,9 +61,11 @@ from PySide6.QtNetwork import (
|
||||
QNetworkProxy,
|
||||
)
|
||||
|
||||
import uuid
|
||||
import ujson
|
||||
import random
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -592,34 +595,68 @@ class ConnectAction(Action):
|
||||
self.errorHttpsProxyConf()
|
||||
else:
|
||||
if validateHttpsProxyServer(httpsProxyServer):
|
||||
|
||||
def fixLoggingRelativePath(attr):
|
||||
# Relative path fails if booting on start up
|
||||
# on Windows, when packed using nuitka...
|
||||
|
||||
# Fix relative path if needed. User cannot feel this operation.
|
||||
|
||||
try:
|
||||
path = self.coreJSON['log'][attr]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if path and (
|
||||
isinstance(path, str) or isinstance(path, bytes)
|
||||
):
|
||||
fix = getAbsolutePath(path)
|
||||
|
||||
logger.info(
|
||||
f'{XrayCore.name()}: {attr} log is specified as \'{path}\'. '
|
||||
f'Fixed to \'{fix}\''
|
||||
)
|
||||
|
||||
self.coreJSON['log'][attr] = fix
|
||||
|
||||
logger.info(f'core {XrayCore.name()} configured')
|
||||
|
||||
fixLoggingRelativePath('access')
|
||||
fixLoggingRelativePath('error')
|
||||
if self.coreJSON.get('log') is None or not isinstance(
|
||||
self.coreJSON['log'], dict
|
||||
):
|
||||
self.coreJSON['log'] = {
|
||||
'access': '',
|
||||
'error': '',
|
||||
'loglevel': 'warning',
|
||||
}
|
||||
|
||||
redirect = str(uuid.uuid4())
|
||||
|
||||
def fixLogObjectPath(logAttr):
|
||||
try:
|
||||
path = self.coreJSON['log'][logAttr]
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
self.coreJSON['log'][logAttr] = path = ''
|
||||
|
||||
if not isinstance(path, str) and not isinstance(path, bytes):
|
||||
self.coreJSON['log'][logAttr] = path = ''
|
||||
|
||||
if path == '':
|
||||
if (
|
||||
isPythonw()
|
||||
and StdoutRedirectHelper.TemporaryDir.isValid()
|
||||
):
|
||||
# Redirect implementation
|
||||
self.coreJSON['log'][
|
||||
logAttr
|
||||
] = StdoutRedirectHelper.TemporaryDir.filePath(redirect)
|
||||
else:
|
||||
# Relative path fails if booting on start up
|
||||
# on Windows, when packed using nuitka...
|
||||
|
||||
# Fix relative path if needed. User cannot feel this operation.
|
||||
self.coreJSON['log'][logAttr] = getAbsolutePath(path)
|
||||
|
||||
result = self.coreJSON['log'][logAttr]
|
||||
|
||||
if result:
|
||||
try:
|
||||
# Create a new file
|
||||
with open(result, 'x'):
|
||||
pass
|
||||
except FileExistsError:
|
||||
pass
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
logger.info(
|
||||
f'{XrayCore.name()}: {logAttr} log is specified as \'{path}\'. '
|
||||
f'Fixed to \'{result}\''
|
||||
)
|
||||
|
||||
# Fix logObject
|
||||
for attr in ['access', 'error']:
|
||||
fixLogObjectPath(attr)
|
||||
|
||||
routing = APP().Routing
|
||||
|
||||
|
||||
+95
-32
@@ -16,14 +16,16 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from Furious.Utility.Constants import APP, PLATFORM, LogType
|
||||
from Furious.Utility.Utility import getAbsolutePath
|
||||
from Furious.Utility.Utility import getAbsolutePath, isPythonw
|
||||
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtTest import QTest
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import ujson
|
||||
import logging
|
||||
import functools
|
||||
import threading
|
||||
@@ -33,6 +35,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Core:
|
||||
class ExitCode:
|
||||
ConfigurationError = 23
|
||||
# Windows: 4294967295. Darwin, Linux: 255 (-1)
|
||||
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
|
||||
# Windows shutting down
|
||||
SystemShuttingDown = 0x40010004
|
||||
|
||||
def __init__(self, *args, exitCallback=None, **kwargs):
|
||||
self._process = None
|
||||
self._exitCallback = exitCallback
|
||||
@@ -66,6 +75,12 @@ class Core:
|
||||
def registerExitCallback(self, exitCallback):
|
||||
self._exitCallback = exitCallback
|
||||
|
||||
def isAlive(self):
|
||||
if isinstance(self._process, multiprocessing.Process):
|
||||
return self._process.is_alive()
|
||||
else:
|
||||
return False
|
||||
|
||||
def checkAlive(self):
|
||||
assert isinstance(self._process, multiprocessing.Process)
|
||||
|
||||
@@ -103,10 +118,7 @@ class Core:
|
||||
self._daemonTimer.start()
|
||||
|
||||
def stop(self):
|
||||
if (
|
||||
isinstance(self._process, multiprocessing.Process)
|
||||
and self._process.is_alive()
|
||||
):
|
||||
if self.isAlive():
|
||||
self._stdoutTimer.stop()
|
||||
self._daemonTimer.stop()
|
||||
|
||||
@@ -129,22 +141,6 @@ class Core:
|
||||
class StdoutRedirectHelper:
|
||||
TemporaryDir = QtCore.QTemporaryDir()
|
||||
|
||||
@staticmethod
|
||||
def isRealFile(file):
|
||||
if not hasattr(file, 'fileno'):
|
||||
return False
|
||||
|
||||
try:
|
||||
tmp = os.dup(file.fileno())
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
return False
|
||||
else:
|
||||
os.close(tmp)
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def launch(msgQueue, entrypoint, redirect):
|
||||
if not callable(entrypoint):
|
||||
@@ -154,8 +150,7 @@ class StdoutRedirectHelper:
|
||||
not StdoutRedirectHelper.TemporaryDir.isValid()
|
||||
or not redirect
|
||||
# pythonw.exe
|
||||
or not StdoutRedirectHelper.isRealFile(sys.__stdout__)
|
||||
or not StdoutRedirectHelper.isRealFile(sys.__stderr__)
|
||||
or isPythonw()
|
||||
):
|
||||
# Call entrypoint directly
|
||||
entrypoint()
|
||||
@@ -204,14 +199,82 @@ def startXrayCore(json, msgQueue):
|
||||
while True:
|
||||
pass
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
if xray.__version__ <= '1.8.4':
|
||||
redirect = False
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
StdoutRedirectHelper.launch(
|
||||
msgQueue, lambda: xray.startFromJSON(json), redirect
|
||||
)
|
||||
# Can be redirected
|
||||
|
||||
if not isPythonw():
|
||||
StdoutRedirectHelper.launch(
|
||||
msgQueue, lambda: xray.startFromJSON(json), redirect
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if not redirect:
|
||||
xray.startFromJSON(json)
|
||||
|
||||
return
|
||||
|
||||
def xrayPythonwProduceMsg():
|
||||
try:
|
||||
jsonObject = ujson.loads(json)
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
xray.startFromJSON(json)
|
||||
|
||||
return
|
||||
|
||||
loggerPath = []
|
||||
fileStream = []
|
||||
|
||||
for attr in ['access', 'error']:
|
||||
try:
|
||||
path = jsonObject['log'][attr]
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
continue
|
||||
|
||||
if path not in loggerPath:
|
||||
loggerPath.append(path)
|
||||
|
||||
try:
|
||||
stream = open(path, 'rb')
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
else:
|
||||
stream.seek(0, io.SEEK_END)
|
||||
|
||||
fileStream.append(stream)
|
||||
|
||||
def produceMsg():
|
||||
while True:
|
||||
for file in fileStream:
|
||||
for line in iter(file.readline, b''):
|
||||
if line and not line.isspace():
|
||||
try:
|
||||
msgQueue.put_nowait(line.decode('utf-8', 'replace'))
|
||||
except Exception:
|
||||
# Any non-exit exceptions
|
||||
|
||||
pass
|
||||
|
||||
try:
|
||||
msgThread = threading.Thread(target=produceMsg, daemon=True)
|
||||
msgThread.start()
|
||||
|
||||
xray.startFromJSON(json)
|
||||
finally:
|
||||
for stream in fileStream:
|
||||
stream.close()
|
||||
|
||||
xrayPythonwProduceMsg()
|
||||
|
||||
|
||||
class XrayCore(Core):
|
||||
@@ -253,10 +316,10 @@ def startHysteria1(json, rule, mmdb, msgQueue):
|
||||
while True:
|
||||
pass
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
if hysteria.__version__ <= '1.3.5':
|
||||
redirect = False
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
StdoutRedirectHelper.launch(
|
||||
msgQueue, lambda: hysteria.startFromJSON(json, rule, mmdb), redirect
|
||||
@@ -339,10 +402,10 @@ def startHysteria2(json, msgQueue):
|
||||
while True:
|
||||
pass
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
if hysteria2.__version__ <= '2.0.0.1':
|
||||
redirect = False
|
||||
else:
|
||||
redirect = True
|
||||
|
||||
StdoutRedirectHelper.launch(
|
||||
msgQueue, lambda: hysteria2.startFromJSON(json), redirect
|
||||
|
||||
Reference in New Issue
Block a user