mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-06-03 09:01:13 +03:00
EDMarketConnector: Change 'no other .exe runnig?' checks
* This is now done even before the stdout/err redirect. * The function is now called no_other_instance_running() to make the conditional use of it read more naturally. * For now this *append* logs to the plain log file. **BUT** any subsequent write by the already-running process will be over the top of this, so a future commit will use a popup instead. # Conflicts: # EDMarketConnector.py
This commit is contained in:
parent
3a57128e28
commit
248493c16a
@ -18,9 +18,96 @@ from typing import TYPE_CHECKING, Any, Mapping, Optional, Tuple
|
|||||||
|
|
||||||
from constants import applongname, appname, protocolhandler_redirect
|
from constants import applongname, appname, protocolhandler_redirect
|
||||||
|
|
||||||
# config will now cause an appname logger to be set up, so we need the
|
# TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup
|
||||||
# console redirect before this
|
# TODO: Move enforce_single_instance() call here ? But then how do we notify
|
||||||
if __name__ == '__main__':
|
# the user if they already have a process ?
|
||||||
|
# We could propagate what that found all the way up and then do the stderr/out redirect
|
||||||
|
# in a non-truncate manner. Need to double-check the existing process won't then overwrite
|
||||||
|
# due to its seek position.
|
||||||
|
def no_other_instance_running() -> bool: # noqa: CCR001
|
||||||
|
"""
|
||||||
|
Ensure only one copy of the app is running under this user account.
|
||||||
|
|
||||||
|
OSX does this automatically.
|
||||||
|
|
||||||
|
:returns: True if we are the single instance, else False.
|
||||||
|
"""
|
||||||
|
# TODO: Linux implementation
|
||||||
|
if platform == 'win32':
|
||||||
|
import ctypes
|
||||||
|
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR
|
||||||
|
|
||||||
|
EnumWindows = ctypes.windll.user32.EnumWindows # noqa: N806
|
||||||
|
GetClassName = ctypes.windll.user32.GetClassNameW # noqa: N806
|
||||||
|
GetClassName.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||||
|
GetWindowText = ctypes.windll.user32.GetWindowTextW # noqa: N806
|
||||||
|
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
||||||
|
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW # noqa: N806
|
||||||
|
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd # noqa: N806
|
||||||
|
|
||||||
|
SW_RESTORE = 9 # noqa: N806
|
||||||
|
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow # noqa: N806
|
||||||
|
ShowWindow = ctypes.windll.user32.ShowWindow # noqa: N806
|
||||||
|
ShowWindowAsync = ctypes.windll.user32.ShowWindowAsync # noqa: N806
|
||||||
|
|
||||||
|
COINIT_MULTITHREADED = 0 # noqa: N806,F841
|
||||||
|
COINIT_APARTMENTTHREADED = 0x2 # noqa: N806
|
||||||
|
COINIT_DISABLE_OLE1DDE = 0x4 # noqa: N806
|
||||||
|
CoInitializeEx = ctypes.windll.ole32.CoInitializeEx # noqa: N806
|
||||||
|
|
||||||
|
ShellExecute = ctypes.windll.shell32.ShellExecuteW # noqa: N806
|
||||||
|
ShellExecute.argtypes = [HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT]
|
||||||
|
|
||||||
|
def window_title(h):
|
||||||
|
if h:
|
||||||
|
text_length = GetWindowTextLength(h) + 1
|
||||||
|
buf = ctypes.create_unicode_buffer(text_length)
|
||||||
|
if GetWindowText(h, buf, text_length):
|
||||||
|
return buf.value
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
|
||||||
|
def enumwindowsproc(window_handle, l_param):
|
||||||
|
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
|
||||||
|
cls = ctypes.create_unicode_buffer(257)
|
||||||
|
if GetClassName(window_handle, cls, 257) \
|
||||||
|
and cls.value == 'TkTopLevel' \
|
||||||
|
and window_title(window_handle) == applongname \
|
||||||
|
and GetProcessHandleFromHwnd(window_handle):
|
||||||
|
# If GetProcessHandleFromHwnd succeeds then the app is already running as this user
|
||||||
|
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler.redirect):
|
||||||
|
CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
|
||||||
|
# Wait for it to be responsive to avoid ShellExecute recursing
|
||||||
|
ShowWindow(window_handle, SW_RESTORE)
|
||||||
|
ShellExecute(0, None, sys.argv[1], None, None, SW_RESTORE)
|
||||||
|
|
||||||
|
else:
|
||||||
|
ShowWindowAsync(window_handle, SW_RESTORE)
|
||||||
|
SetForegroundWindow(window_handle)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return EnumWindows(enumwindowsproc, 0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if not no_other_instance_running():
|
||||||
|
# There's a copy already running. We want to inform the user by
|
||||||
|
# **appending** to the log file, not truncating it.
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# By default py2exe tries to write log to dirname(sys.executable) which fails when installed
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# unbuffered not allowed for text in python3, so use `1 for line buffering
|
||||||
|
sys.stdout = sys.stderr = open(join(tempfile.gettempdir(), f'{appname}.log'), mode='a', buffering=1)
|
||||||
|
|
||||||
|
# Logging isn't set up yet
|
||||||
|
print("An EDMarketConnector.exe process was already running, exiting.")
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# Keep this as the very first code run to be as sure as possible of no
|
# Keep this as the very first code run to be as sure as possible of no
|
||||||
# output until after this redirect is done, if needed.
|
# output until after this redirect is done, if needed.
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
@ -1306,77 +1393,6 @@ class AppWindow(object):
|
|||||||
self.blank_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW)
|
self.blank_menubar.grid(row=0, columnspan=2, sticky=tk.NSEW)
|
||||||
|
|
||||||
|
|
||||||
def enforce_single_instance() -> None: # noqa: CCR001
|
|
||||||
"""
|
|
||||||
Ensure only one copy of the app is running under this user account.
|
|
||||||
|
|
||||||
OSX does this automatically.
|
|
||||||
"""
|
|
||||||
# TODO: Linux implementation
|
|
||||||
if platform == 'win32':
|
|
||||||
import ctypes
|
|
||||||
from ctypes.wintypes import BOOL, HWND, INT, LPARAM, LPCWSTR, LPWSTR
|
|
||||||
|
|
||||||
EnumWindows = ctypes.windll.user32.EnumWindows # noqa: N806
|
|
||||||
GetClassName = ctypes.windll.user32.GetClassNameW # noqa: N806
|
|
||||||
GetClassName.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
|
||||||
GetWindowText = ctypes.windll.user32.GetWindowTextW # noqa: N806
|
|
||||||
GetWindowText.argtypes = [HWND, LPWSTR, ctypes.c_int]
|
|
||||||
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW # noqa: N806
|
|
||||||
GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd # noqa: N806
|
|
||||||
|
|
||||||
SW_RESTORE = 9 # noqa: N806
|
|
||||||
SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow # noqa: N806
|
|
||||||
ShowWindow = ctypes.windll.user32.ShowWindow # noqa: N806
|
|
||||||
ShowWindowAsync = ctypes.windll.user32.ShowWindowAsync # noqa: N806
|
|
||||||
|
|
||||||
COINIT_MULTITHREADED = 0 # noqa: N806,F841
|
|
||||||
COINIT_APARTMENTTHREADED = 0x2 # noqa: N806
|
|
||||||
COINIT_DISABLE_OLE1DDE = 0x4 # noqa: N806
|
|
||||||
CoInitializeEx = ctypes.windll.ole32.CoInitializeEx # noqa: N806
|
|
||||||
|
|
||||||
ShellExecute = ctypes.windll.shell32.ShellExecuteW # noqa: N806
|
|
||||||
ShellExecute.argtypes = [HWND, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, INT]
|
|
||||||
|
|
||||||
def window_title(h):
|
|
||||||
if h:
|
|
||||||
text_length = GetWindowTextLength(h) + 1
|
|
||||||
buf = ctypes.create_unicode_buffer(text_length)
|
|
||||||
if GetWindowText(h, buf, text_length):
|
|
||||||
return buf.value
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
@ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
|
|
||||||
def enumwindowsproc(window_handle, l_param):
|
|
||||||
# class name limited to 256 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms633576
|
|
||||||
cls = ctypes.create_unicode_buffer(257)
|
|
||||||
if GetClassName(window_handle, cls, 257) \
|
|
||||||
and cls.value == 'TkTopLevel' \
|
|
||||||
and window_title(window_handle) == applongname \
|
|
||||||
and GetProcessHandleFromHwnd(window_handle):
|
|
||||||
# If GetProcessHandleFromHwnd succeeds then the app is already running as this user
|
|
||||||
if len(sys.argv) > 1 and sys.argv[1].startswith(protocolhandler.redirect):
|
|
||||||
# logger.debug('Browser invoked us directly with auth response. '
|
|
||||||
# 'Forwarding the response to the other app instance.')
|
|
||||||
CoInitializeEx(0, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
|
|
||||||
# Wait for it to be responsive to avoid ShellExecute recursing
|
|
||||||
ShowWindow(window_handle, SW_RESTORE)
|
|
||||||
ShellExecute(0, None, sys.argv[1], None, None, SW_RESTORE)
|
|
||||||
|
|
||||||
else:
|
|
||||||
ShowWindowAsync(window_handle, SW_RESTORE)
|
|
||||||
SetForegroundWindow(window_handle)
|
|
||||||
|
|
||||||
# logger.info(f'A running {applongname} process was found, exiting.')
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not EnumWindows(enumwindowsproc, 0):
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def test_logging() -> None:
|
def test_logging() -> None:
|
||||||
"""Simple test of top level logging."""
|
"""Simple test of top level logging."""
|
||||||
logger.debug('Test from EDMarketConnector.py top-level test_logging()')
|
logger.debug('Test from EDMarketConnector.py top-level test_logging()')
|
||||||
@ -1394,7 +1410,29 @@ Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}'''
|
|||||||
|
|
||||||
|
|
||||||
# Run the app
|
# Run the app
|
||||||
if __name__ == "__main__": # noqa C901
|
if __name__ == "__main__":
|
||||||
|
# Command-line arguments
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=appname,
|
||||||
|
description="Utilises Elite Dangerous Journal files and the Frontier "
|
||||||
|
"Companion API (CAPI) service to gather data about a "
|
||||||
|
"player's state and actions to upload to third-party sites "
|
||||||
|
"such as EDSM, Inara.cz and EDDB."
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('--trace',
|
||||||
|
help='Set the Debug logging loglevel to TRACE',
|
||||||
|
action='store_true',
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.trace:
|
||||||
|
logger.setLevel(logging.TRACE)
|
||||||
|
edmclogger.set_channels_loglevel(logging.TRACE)
|
||||||
|
else:
|
||||||
|
edmclogger.set_channels_loglevel(logging.DEBUG)
|
||||||
|
|
||||||
logger.info(f'Startup v{appversion} : Running on Python v{sys.version}')
|
logger.info(f'Startup v{appversion} : Running on Python v{sys.version}')
|
||||||
logger.debug(f'''Platform: {sys.platform} {sys.platform == "win32" and sys.getwindowsversion()}
|
logger.debug(f'''Platform: {sys.platform} {sys.platform == "win32" and sys.getwindowsversion()}
|
||||||
argv[0]: {sys.argv[0]}
|
argv[0]: {sys.argv[0]}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user