Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e37a6f8

Browse files
authored
move settings files to app dir in any case (#907)
* move settings files to app dir in any case * test + lint * move settings out of gui, because it contains both * updated docs for write_error_log * lint
1 parent b3c7fa1 commit e37a6f8

8 files changed

Lines changed: 89 additions & 63 deletions

File tree

birdnet_analyzer/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,4 @@
3535
UPSAMPLING_MODES = Literal["repeat", "mean", "smote"]
3636
TRAINED_MODEL_OUTPUT_FORMATS = Literal["tflite", "raven", "both"]
3737
TRAINED_MODEL_SAVE_MODES = Literal["replace", "append"]
38-
ERROR_LOG_FILE: str = os.path.join(SCRIPT_DIR, "error_log.txt")
3938
AUTOTUNE_METRICS = Literal["val_loss", "val_AUPRC", "val_AUROC"]

birdnet_analyzer/gui/localization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import os
44

5-
from birdnet_analyzer.gui import settings
5+
from birdnet_analyzer import settings
66

77
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
88
LANGUAGE_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), "lang")

birdnet_analyzer/gui/species.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import birdnet_analyzer.gui.localization as loc
66
import birdnet_analyzer.gui.utils as gu
7-
from birdnet_analyzer.gui import settings
7+
from birdnet_analyzer import settings
88

99

1010
@gu.gui_runtime_error_handler

birdnet_analyzer/gui/utils.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@
1414
import webview
1515
from birdnet.globals import MODEL_LANGUAGE_EN_US, MODEL_LANGUAGES
1616

17-
import birdnet_analyzer.config as cfg
1817
import birdnet_analyzer.gui.localization as loc
19-
from birdnet_analyzer import utils
20-
from birdnet_analyzer.gui import settings
18+
from birdnet_analyzer import settings, utils
2119

2220
warnings.filterwarnings("ignore")
2321
loc.load_local_state()
@@ -247,7 +245,7 @@ def build_footer():
247245
<div style='display: flex; justify-content: space-around; align-items: center; padding: 10px; text-align: center'>
248246
<div>
249247
<div style="display: flex;flex-direction: row;">GUI version:&nbsp<span
250-
id="current-version">{os.environ["GUI_VERSION"] if utils.FROZEN else "main"}</span><span
248+
id="current-version">{os.environ["GUI_VERSION"] if settings.FROZEN else "main"}</span><span
251249
style="display: none" id="update-available"><a>+</a></span></div>
252250
<div>Model version: 2.4</div>
253251
</div>
@@ -310,7 +308,7 @@ def build_settings():
310308
label=loc.localize("settings-tab-error-log-textbox-label"),
311309
info=(
312310
f"{loc.localize('settings-tab-error-log-textbox-info-path')}: "
313-
f"{cfg.ERROR_LOG_FILE}"
311+
f"{settings.ERROR_LOG_FILE}"
314312
),
315313
interactive=False,
316314
placeholder=loc.localize("settings-tab-error-log-textbox-placeholder"),
@@ -329,8 +327,8 @@ def on_theme_change(value):
329327
_WINDOW.load_url(_URL.rstrip("/") + f"?__theme={value}") # type: ignore
330328

331329
def on_tab_select(value: gr.SelectData):
332-
if value.selected and os.path.exists(cfg.ERROR_LOG_FILE):
333-
with open(cfg.ERROR_LOG_FILE, mode="rb") as f:
330+
if value.selected and os.path.exists(settings.ERROR_LOG_FILE):
331+
with open(settings.ERROR_LOG_FILE, mode="rb") as f:
334332
lines = [line.decode("utf-8", errors="ignore") for line in f]
335333
last_100_lines = lines[-100:]
336334

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import json
22
import os
33
import sys
4+
import traceback
45
from 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":
@@ -17,24 +18,27 @@
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

2936
FALLBACK_LANGUAGE = "en"
3037
SCRIPT_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")
3440
LANG_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

4044
def 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

98104
def 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

114121
def 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+
)

birdnet_analyzer/utils.py

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@
22

33
import itertools
44
import os
5-
import sys
6-
import traceback
75
from pathlib import Path
86

9-
from birdnet_analyzer.config import ALLOWED_FILETYPES, CODES_FILE, ERROR_LOG_FILE
7+
from birdnet_analyzer.config import ALLOWED_FILETYPES, CODES_FILE
8+
from birdnet_analyzer.settings import write_error_log
109

1110
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
12-
FROZEN = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
1311

1412

1513
def runtime_error_handler(f):
@@ -193,34 +191,6 @@ def list_subdirectories(path: str):
193191
return filter(lambda el: os.path.isdir(os.path.join(path, el)), os.listdir(path))
194192

195193

196-
def clear_error_log():
197-
"""Clears the error log file.
198-
199-
For debugging purposes.
200-
"""
201-
if os.path.isfile(ERROR_LOG_FILE):
202-
os.remove(ERROR_LOG_FILE)
203-
204-
205-
def write_error_log(ex: Exception):
206-
"""Writes an exception to the error log.
207-
208-
Formats the stacktrace and writes it in the error log file configured in the config.
209-
210-
Args:
211-
ex: An exception that occurred.
212-
"""
213-
import datetime
214-
215-
with open(ERROR_LOG_FILE, "a") as elog:
216-
elog.write(
217-
datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
218-
+ "\n"
219-
+ "".join(traceback.TracebackException.from_exception(ex).format())
220-
+ "\n"
221-
)
222-
223-
224194
def load_codes() -> dict[str, str]:
225195
"""Loads the eBird codes.
226196

tests/gui/test_language.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections import defaultdict
33
from pathlib import Path
44

5-
from birdnet_analyzer.gui.settings import LANG_DIR
5+
from birdnet_analyzer.settings import LANG_DIR
66

77

88
def test_language_keys():

tests/gui/test_settings.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import importlib
2+
import sys
3+
from pathlib import Path
4+
5+
from birdnet_analyzer import settings
6+
7+
8+
def test_gui_runtime_files_use_user_appdir_when_not_frozen(monkeypatch, tmp_path):
9+
monkeypatch.delattr(sys, "frozen", raising=False)
10+
monkeypatch.delattr(sys, "_MEIPASS", raising=False)
11+
monkeypatch.setattr(sys, "platform", "linux")
12+
monkeypatch.setattr(Path, "home", lambda: tmp_path)
13+
14+
reloaded_settings = importlib.reload(settings)
15+
expected_appdir = tmp_path / ".local" / "share" / "BirdNET-Analyzer-GUI"
16+
17+
assert expected_appdir == reloaded_settings.APPDIR
18+
assert Path(reloaded_settings.GUI_SETTINGS_PATH) == (
19+
expected_appdir / "gui-settings.json"
20+
)
21+
assert Path(reloaded_settings.STATE_SETTINGS_PATH) == (
22+
expected_appdir / "state.json"
23+
)
24+
assert Path(reloaded_settings.ERROR_LOG_FILE) == expected_appdir / "error_log.txt"
25+
26+
reloaded_settings.ensure_settings_file()
27+
reloaded_settings.set_state("train-data-dir", "/tmp/train")
28+
reloaded_settings.write_error_log(RuntimeError("gui path test"))
29+
30+
assert (expected_appdir / "gui-settings.json").exists()
31+
assert (expected_appdir / "state.json").exists()
32+
assert (expected_appdir / "error_log.txt").exists()

0 commit comments

Comments
 (0)