11import json
22import os
33import sys
4+ import traceback
45from pathlib import Path
56
6- import birdnet_analyzer . config as cfg
7- from birdnet_analyzer import utils
7+ APP_NAME = "BirdNET-Analyzer-GUI"
8+ FROZEN = getattr ( sys , "frozen" , False ) and hasattr ( sys , "_MEIPASS" )
89
9- if utils . FROZEN :
10- # divert stdout & stderr to logs.txt file since we have no console when deployed
10+
11+ def _get_user_data_dir () -> Path :
1112 userdir = Path .home ()
1213
1314 if sys .platform == "win32" :
1718 elif sys .platform == "darwin" :
1819 userdir /= "Library/Application Support"
1920
20- APPDIR = userdir / "BirdNET-Analyzer-GUI"
21+ return userdir / APP_NAME
22+
23+
24+ APPDIR = _get_user_data_dir ()
2125
26+
27+ def _ensure_appdir_exists () -> None :
2228 APPDIR .mkdir (parents = True , exist_ok = True )
2329
24- sys .stderr = sys .stdout = open (str (APPDIR / "logs.txt" ), "a" ) # noqa: SIM115
25- cfg .ERROR_LOG_FILE = str (APPDIR / os .path .basename (cfg .ERROR_LOG_FILE ))
26- else :
27- APPDIR = ""
30+
31+ if FROZEN :
32+ # divert stdout & stderr to logs.txt file since we have no console when deployed
33+ _ensure_appdir_exists ()
34+ sys .stderr = sys .stdout = open (APPDIR / "logs.txt" , "a" ) # noqa: SIM115
2835
2936FALLBACK_LANGUAGE = "en"
3037SCRIPT_DIR = os .path .abspath (os .path .dirname (__file__ ))
31- GUI_SETTINGS_PATH = os .path .join (
32- APPDIR if utils .FROZEN else os .path .dirname (SCRIPT_DIR ), "gui-settings.json"
33- )
38+ ERROR_LOG_FILE = str (APPDIR / "error_log.txt" )
39+ GUI_SETTINGS_PATH = str (APPDIR / "gui-settings.json" )
3440LANG_DIR = str (Path (SCRIPT_DIR ).parent / "lang" )
35- STATE_SETTINGS_PATH = os .path .join (
36- APPDIR if utils .FROZEN else os .path .dirname (SCRIPT_DIR ), "state.json"
37- )
41+ STATE_SETTINGS_PATH = str (APPDIR / "state.json" )
3842
3943
4044def get_state_dict () -> dict :
@@ -54,11 +58,12 @@ def get_state_dict() -> dict:
5458 return json .load (f )
5559 except FileNotFoundError :
5660 try :
61+ _ensure_appdir_exists ()
5762 with open (STATE_SETTINGS_PATH , "w" , encoding = "utf-8" ) as f :
5863 json .dump ({}, f )
5964 return {}
6065 except Exception as e :
61- utils . write_error_log (e )
66+ write_error_log (e )
6267 return {}
6368
6469
@@ -89,10 +94,11 @@ def set_state(key: str, value: str):
8994 state = get_state_dict ()
9095 state [key ] = value
9196
92- with open (STATE_SETTINGS_PATH , "w" ) as f :
97+ _ensure_appdir_exists ()
98+ with open (STATE_SETTINGS_PATH , "w" , encoding = "utf-8" ) as f :
9399 json .dump (state , f , indent = 4 )
94100 except Exception as e :
95- utils . write_error_log (e )
101+ write_error_log (e )
96102
97103
98104def ensure_settings_file ():
@@ -104,11 +110,12 @@ def ensure_settings_file():
104110 """
105111 if not os .path .exists (GUI_SETTINGS_PATH ):
106112 try :
107- with open (GUI_SETTINGS_PATH , "w" ) as f :
113+ _ensure_appdir_exists ()
114+ with open (GUI_SETTINGS_PATH , "w" , encoding = "utf-8" ) as f :
108115 settings = {"language-id" : FALLBACK_LANGUAGE , "theme" : "light" }
109116 f .write (json .dumps (settings , indent = 4 ))
110117 except Exception as e :
111- utils . write_error_log (e )
118+ write_error_log (e )
112119
113120
114121def get_setting (key , default = None ):
@@ -154,3 +161,23 @@ def theme():
154161 current_time = get_setting ("theme" , "light" )
155162
156163 return current_time if current_time in options else "light"
164+
165+
166+ def write_error_log (ex : Exception ):
167+ """Writes an exception to the error log.
168+
169+ Formats the stacktrace and writes it in the error log file.
170+
171+ Args:
172+ ex: An exception that occurred.
173+ """
174+ import datetime
175+
176+ Path (ERROR_LOG_FILE ).parent .mkdir (parents = True , exist_ok = True )
177+ with open (ERROR_LOG_FILE , "a" , encoding = "utf-8" ) as elog :
178+ elog .write (
179+ datetime .datetime .now ().strftime ("[%Y-%m-%d %H:%M:%S]" )
180+ + "\n "
181+ + "" .join (traceback .TracebackException .from_exception (ex ).format ())
182+ + "\n "
183+ )
0 commit comments