From 8b5e5e73dec3297504cf3544c690343af9bdcd64 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 15 Sep 2020 10:24:38 +0100 Subject: [PATCH] Locale: More detailed login at startup around changes As we might run into some special cases with users we need to log in more detail what the locale is around our changes. NB: Also contains some misc. PyCharm-enacted formatting changes, white space, wrapping etc. --- EDMarketConnector.py | 115 ++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 4d25fe17..a3e46aac 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -23,6 +23,7 @@ if __name__ == "__main__": 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='wt', buffering=1) @@ -45,6 +46,7 @@ if __debug__: if platform != 'win32': import pdb import signal + signal.signal(signal.SIGTERM, lambda sig, frame: pdb.Pdb().set_trace(frame)) import companion @@ -61,7 +63,6 @@ from protocol import protocolhandler from dashboard import dashboard from theme import theme - SERVER_RETRY = 5 # retry pause for Companion servers [s] SHIPYARD_HTML_TEMPLATE = """ @@ -81,7 +82,6 @@ SHIPYARD_HTML_TEMPLATE = """ class AppWindow(object): - # Tkinter Event types EVENT_KEYPRESS = 2 EVENT_BUTTON = 4 @@ -104,10 +104,14 @@ class AppWindow(object): if platform == 'win32': self.w.wm_iconbitmap(default='EDMarketConnector.ico') else: - self.w.tk.call('wm', 'iconphoto', self.w, '-default', tk.PhotoImage(file=join(config.respath, 'EDMarketConnector.png'))) # noqa: E501 - self.theme_icon = tk.PhotoImage(data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 - self.theme_minimize = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 - self.theme_close = tk.BitmapImage(data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 + self.w.tk.call('wm', 'iconphoto', self.w, '-default', + tk.PhotoImage(file=join(config.respath, 'EDMarketConnector.png'))) # noqa: E501 + self.theme_icon = tk.PhotoImage( + data='R0lGODlhFAAQAMZQAAoKCQoKCgsKCQwKCQsLCgwLCg4LCQ4LCg0MCg8MCRAMCRANChINCREOChIOChQPChgQChgRCxwTCyYVCSoXCS0YCTkdCTseCT0fCTsjDU0jB0EnDU8lB1ElB1MnCFIoCFMoCEkrDlkqCFwrCGEuCWIuCGQvCFs0D1w1D2wyCG0yCF82D182EHE0CHM0CHQ1CGQ5EHU2CHc3CHs4CH45CIA6CIE7CJdECIdLEolMEohQE5BQE41SFJBTE5lUE5pVE5RXFKNaFKVbFLVjFbZkFrxnFr9oFsNqFsVrF8RsFshtF89xF9NzGNh1GNl2GP+KG////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yH5BAEKAH8ALAAAAAAUABAAAAeegAGCgiGDhoeIRDiIjIZGKzmNiAQBQxkRTU6am0tPCJSGShuSAUcLoIIbRYMFra4FAUgQAQCGJz6CDQ67vAFJJBi0hjBBD0w9PMnJOkAiJhaIKEI7HRoc19ceNAolwbWDLD8uAQnl5ga1I9CHEjEBAvDxAoMtFIYCBy+kFDKHAgM3ZtgYSLAGgwkp3pEyBOJCC2ELB31QATGioAoVAwEAOw==') # noqa: E501 + self.theme_minimize = tk.BitmapImage( + data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x3f,\n 0xfc, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 + self.theme_close = tk.BitmapImage( + data='#define im_width 16\n#define im_height 16\nstatic unsigned char im_bits[] = {\n 0x00, 0x00, 0x00, 0x00, 0x0c, 0x30, 0x1c, 0x38, 0x38, 0x1c, 0x70, 0x0e,\n 0xe0, 0x07, 0xc0, 0x03, 0xc0, 0x03, 0xe0, 0x07, 0x70, 0x0e, 0x38, 0x1c,\n 0x1c, 0x38, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00 };\n') # noqa: E501 frame = tk.Frame(self.w, name=appname.lower()) frame.grid(sticky=tk.NSEW) @@ -152,7 +156,8 @@ class AppWindow(object): row = frame.grid_size()[1] self.button.grid(row=row, columnspan=2, sticky=tk.NSEW) self.theme_button.grid(row=row, columnspan=2, sticky=tk.NSEW) - theme.register_alternate((self.button, self.theme_button, self.theme_button), {'row': row, 'columnspan': 2, 'sticky': tk.NSEW}) # noqa: E501 + theme.register_alternate((self.button, self.theme_button, self.theme_button), + {'row': row, 'columnspan': 2, 'sticky': tk.NSEW}) # noqa: E501 self.status.grid(columnspan=2, sticky=tk.EW) self.button.bind('', self.getandsend) theme.button_bind(self.theme_button, self.getandsend) @@ -291,6 +296,7 @@ class AppWindow(object): # Check that the titlebar will be at least partly on screen import ctypes from ctypes.wintypes import POINT + # https://msdn.microsoft.com/en-us/library/dd145064 MONITOR_DEFAULTTONULL = 0 # noqa: N806 if ctypes.windll.user32.MonitorFromPoint(POINT(int(match.group(1)) + 16, int(match.group(2)) + 16), @@ -322,6 +328,7 @@ class AppWindow(object): # Load updater after UI creation (for WinSparkle) import update + if getattr(sys, 'frozen', False): # Running in frozen .exe, so use (Win)Sparkle self.updater = update.Updater(tkroot=self.w, provider='external') @@ -453,7 +460,7 @@ class AppWindow(object): if not retrying: if time() < self.holdofftime: # Was invoked by key while in cooldown self.status['text'] = '' - if play_sound and (self.holdofftime-time()) < companion.holdoff*0.75: + if play_sound and (self.holdofftime - time()) < companion.holdoff * 0.75: hotkeymgr.play_bad() # Don't play sound in first few seconds to prevent repeats return elif play_sound: @@ -493,7 +500,7 @@ class AppWindow(object): if isdir('dump'): with open('dump/{system}{station}.{timestamp}.json'.format( system=data['lastSystem']['name'], - station=data['commander'].get('docked') and '.'+data['lastStarport']['name'] or '', + station=data['commander'].get('docked') and '.' + data['lastStarport']['name'] or '', timestamp=strftime('%Y-%m-%dT%H.%M.%S', localtime())), 'wb') as h: h.write(json.dumps(data, ensure_ascii=False, @@ -525,7 +532,7 @@ class AppWindow(object): self.status['text'] = _("You're not docked at a station!") play_bad = True # Ignore possibly missing shipyard info - elif (config.getint('output') & config.OUT_MKT_EDDN)\ + elif (config.getint('output') & config.OUT_MKT_EDDN) \ and not (data['lastStarport'].get('commodities') or data['lastStarport'].get('modules')): if not self.status['text']: self.status['text'] = _("Station doesn't have anything!") @@ -585,12 +592,12 @@ class AppWindow(object): if not data['commander'].get('docked'): # might have un-docked while we were waiting for retry in which case station data is unreliable pass - elif (data.get('lastSystem', {}).get('name') == monitor.system and + elif (data.get('lastSystem', {}).get('name') == monitor.system and data.get('lastStarport', {}).get('name') == monitor.station and data.get('lastStarport', {}).get('ships', {}).get('shipyard_list')): self.eddn.export_shipyard(data, monitor.is_beta) elif tries > 1: # bogus data - retry - self.w.after(int(SERVER_RETRY * 1000), lambda: self.retry_for_shipyard(tries-1)) + self.w.after(int(SERVER_RETRY * 1000), lambda: self.retry_for_shipyard(tries - 1)) except Exception: pass @@ -600,11 +607,11 @@ class AppWindow(object): def crewroletext(role): # Return translated crew role. Needs to be dynamic to allow for changing language. return { - None: '', - 'Idle': '', + None : '', + 'Idle' : '', 'FighterCon': _('Fighter'), # Multicrew role - 'FireCon': _('Gunner'), # Multicrew role - 'FlightCon': _('Helm'), # Multicrew role + 'FireCon' : _('Gunner'), # Multicrew role + 'FlightCon' : _('Helm'), # Multicrew role }.get(role, role) while True: @@ -626,8 +633,8 @@ class AppWindow(object): self.ship_label['text'] = _('Ship') + ':' # Main window self.ship.configure( text=monitor.state['ShipName'] - or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType']) - or '', + or companion.ship_map.get(monitor.state['ShipType'], monitor.state['ShipType']) + or '', url=self.shipyard_url) else: self.cmdr['text'] = '' @@ -673,7 +680,7 @@ class AppWindow(object): logger.info("Can't start Status monitoring") # Export loadout - if entry['event'] == 'Loadout' and not monitor.state['Captain']\ + if entry['event'] == 'Loadout' and not monitor.state['Captain'] \ and config.getint('output') & config.OUT_SHIP: monitor.export_ship() @@ -690,10 +697,10 @@ class AppWindow(object): hotkeymgr.play_bad() # Auto-Update after docking, but not if auth callback is pending - if entry['event'] in ('StartUp', 'Location', 'Docked')\ - and monitor.station\ - and not config.getint('output') & config.OUT_MKT_MANUAL\ - and config.getint('output') & config.OUT_STATION_ANY\ + if entry['event'] in ('StartUp', 'Location', 'Docked') \ + and monitor.station \ + and not config.getint('output') & config.OUT_MKT_MANUAL \ + and config.getint('output') & config.OUT_STATION_ANY \ and companion.session.state != companion.Session.STATE_AUTH: self.w.after(int(SERVER_RETRY * 1000), self.getandsend) @@ -760,10 +767,10 @@ class AppWindow(object): return f'file://localhost/{file_name}' def system_url(self, system): - return plug.invoke(config.get('system_provider'), 'EDSM', 'system_url', monitor.system) + return plug.invoke(config.get('system_provider'), 'EDSM', 'system_url', monitor.system) def station_url(self, station): - return plug.invoke(config.get('station_provider'), 'eddb', 'station_url', monitor.system, monitor.station) + return plug.invoke(config.get('station_provider'), 'eddb', 'station_url', monitor.system, monitor.station) def cooldown(self): if time() < self.holdofftime: @@ -837,7 +844,7 @@ class AppWindow(object): ############################################################ # version - ttk.Label(frame).grid(row=row, column=0) # spacer + ttk.Label(frame).grid(row=row, column=0) # spacer row += 1 self.appversion_label = tk.Label(frame, text=appversion) self.appversion_label.grid(row=row, column=0, sticky=tk.E) @@ -855,7 +862,7 @@ class AppWindow(object): ############################################################ # - ttk.Label(frame).grid(row=row, column=0) # spacer + ttk.Label(frame).grid(row=row, column=0) # spacer row += 1 self.copyright = tk.Label(frame, text=copyright) self.copyright.grid(row=row, columnspan=3, sticky=tk.EW) @@ -864,7 +871,7 @@ class AppWindow(object): ############################################################ # OK button to close the window - ttk.Label(frame).grid(row=row, column=0) # spacer + ttk.Label(frame).grid(row=row, column=0) # spacer row += 1 button = ttk.Button(frame, text=_('OK'), command=self.apply) button.grid(row=row, column=2, sticky=tk.E) @@ -895,7 +902,7 @@ class AppWindow(object): last_system: str = data.get("lastSystem", {}).get("name", "Unknown") last_starport: str = '' if data['commander'].get('docked'): - last_starport = '.'+data.get('lastStarport', {}).get('name', 'Unknown') + last_starport = '.' + data.get('lastStarport', {}).get('name', 'Unknown') timestamp: str = strftime('%Y-%m-%dT%H.%M.%S', localtime()) f = tkinter.filedialog.asksaveasfilename(parent=self.w, defaultextension=default_extension, @@ -971,6 +978,7 @@ def enforce_single_instance() -> None: if platform == 'win32': import ctypes from ctypes.wintypes import HWND, LPWSTR, LPCWSTR, INT, BOOL, LPARAM + EnumWindows = ctypes.windll.user32.EnumWindows # noqa: N806 GetClassName = ctypes.windll.user32.GetClassNameW # noqa: N806 GetClassName.argtypes = [HWND, LPWSTR, ctypes.c_int] # noqa: N806 @@ -1004,9 +1012,9 @@ def enforce_single_instance() -> None: 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\ + 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): @@ -1028,38 +1036,52 @@ def test_logging(): logger.debug('Test from EDMarketConnector.py top-level test_logging()') -# Run the app -if __name__ == "__main__": - enforce_single_instance() - - from EDMCLogging import logger - logger.info(f'Startup v{appversion} : Running on Python v{sys.version}') - logger.debug(f'''Platform: {sys.platform} -argv[0]: {sys.argv[0]} -exec_prefix: {sys.exec_prefix} -executable: {sys.executable} -sys.path: {sys.path} +def log_locale(prefix: str) -> None: + logger.debug(f'''Locale: {prefix} Locale LC_COLLATE: {locale.getlocale(locale.LC_COLLATE)} Locale LC_CTYPE: {locale.getlocale(locale.LC_CTYPE)} Locale LC_MONETARY: {locale.getlocale(locale.LC_MONETARY)} Locale LC_NUMERIC: {locale.getlocale(locale.LC_NUMERIC)} Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}''' -) + ) + + +# Run the app +if __name__ == "__main__": + enforce_single_instance() + + from EDMCLogging import logger + + logger.info(f'Startup v{appversion} : Running on Python v{sys.version}') + logger.debug(f'''Platform: {sys.platform} +argv[0]: {sys.argv[0]} +exec_prefix: {sys.exec_prefix} +executable: {sys.executable} +sys.path: {sys.path}''' + ) # Change locale to a utf8 one - # First make sure the local is actually set as per locale's idea of defaults + # Log what we have at startup + log_locale('Initial Locale') + # Make sure the local is actually set as per locale's idea of defaults locale.setlocale(locale.LC_ALL, '') + log_locale('After LC_ALL defaults set') # Now find out the current locale, mostly the language locale_startup = locale.getlocale(locale.LC_ALL) + logger.debug(f'Locale LC_ALL: {locale_startup}') # Now set that same language, but utf8 encoding (it was probably cp1252 # or equivalent for other languages). locale.setlocale(locale.LC_ALL, (locale_startup[0], 'utf8')) + log_locale('After switching to utf8 encoding (same language)') # TODO: unittests in place of these # logger.debug('Test from __main__') # test_logging() + class A(object): + class B(object): + def __init__(self): logger.debug('A call from A.B.__init__') @@ -1102,7 +1124,8 @@ Locale LC_TIME: {locale.getlocale(locale.LC_TIME)}''' # Now the string should match, so try translation popup_text = _(popup_text) # And substitute in the other words. - popup_text = popup_text.format(PLUGINS=_('Plugins'), FILE=_('File'), SETTINGS=_('Settings'), DISABLED='.disabled') + popup_text = popup_text.format(PLUGINS=_('Plugins'), FILE=_('File'), SETTINGS=_('Settings'), + DISABLED='.disabled') # And now we do need these to be actual \r\n popup_text = popup_text.replace('\\n', '\n') popup_text = popup_text.replace('\\r', '\r')