Skip to content

Commit 5b786e2

Browse files
Merge pull request automatic-ripping-machine#1694 from xieve/fix-read-only
[BUGFIX] Gracefully handle read-only config files
2 parents 2a589aa + eb0a841 commit 5b786e2

6 files changed

Lines changed: 86 additions & 60 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.22.2
1+
2.22.3

arm/config/config.py

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,48 +30,46 @@ def _load_abcde(fp):
3030

3131
# arm config, open and read yaml contents
3232
# handle arm.yaml migration here
33-
# 1. Load both current and template arm.yaml
33+
# Load user config
3434
cur_cfg = _load_config(arm_config_path)
35-
new_cfg = _load_config(os.path.join(cur_cfg['INSTALLPATH'], "setup/arm.yaml"))
35+
# Load template config
36+
arm_config = _load_config(os.path.join(cur_cfg["INSTALLPATH"], "setup/arm.yaml"))
3637

37-
# 2. If the dicts do not have the same number of keys
38-
if len(cur_cfg) != len(new_cfg):
39-
# 3. Update new dict with current values
40-
for key in cur_cfg:
41-
if key in new_cfg:
42-
new_cfg[key] = cur_cfg[key]
38+
# Update the template config with the user's values
39+
arm_config.update(cur_cfg)
4340

44-
# 4. Save the dictionary
45-
with open(
41+
try:
42+
# Save the dictionary
43+
with open(arm_config_path, "w") as settings_file:
44+
with open(
4645
os.path.join(cur_cfg["INSTALLPATH"], "arm/ui/comments.json"),
4746
"r",
48-
) as comments_file:
49-
comments = json.load(comments_file)
50-
51-
arm_cfg = comments['ARM_CFG_GROUPS']['BEGIN'] + "\n\n"
52-
for key, value in dict(new_cfg).items():
53-
# Add any grouping comments
54-
arm_cfg += config_utils.arm_yaml_check_groups(comments, key)
55-
# Check for comments for this key in comments.json, add them if they exist
56-
try:
57-
arm_cfg += "\n" + comments[str(key)] + "\n" if comments[str(key)] != "" else ""
58-
except KeyError:
59-
arm_cfg += "\n"
60-
# test if key value is an int
61-
value = str(value) # just change the type to keep things as expected
62-
try:
63-
post_value = int(value)
64-
arm_cfg += f"{key}: {post_value}\n"
65-
except ValueError:
66-
# Test if value is Boolean
67-
arm_cfg += config_utils.arm_yaml_test_bool(key, value)
68-
69-
# this handles the truncation
70-
with open(arm_config_path, "w") as settings_file:
47+
) as comments_file:
48+
comments = json.load(comments_file)
49+
50+
arm_cfg = comments["ARM_CFG_GROUPS"]["BEGIN"] + "\n\n"
51+
for key, value in dict(arm_config).items():
52+
# Add any grouping comments
53+
arm_cfg += config_utils.arm_yaml_check_groups(comments, key)
54+
# Check for comments for this key in comments.json, add them if they exist
55+
try:
56+
if comment := comments[str(key)]:
57+
arm_cfg += f"\n{comment}\n"
58+
except KeyError:
59+
arm_cfg += "\n"
60+
# test if key value is an int
61+
value = str(value) # just change the type to keep things as expected
62+
try:
63+
post_value = int(value)
64+
arm_cfg += f"{key}: {post_value}\n"
65+
except ValueError:
66+
# Test if value is Boolean
67+
arm_cfg += config_utils.arm_yaml_test_bool(key, value)
68+
7169
settings_file.write(arm_cfg)
7270
settings_file.close()
73-
74-
arm_config = _load_config(arm_config_path)
71+
except OSError:
72+
pass
7573

7674
# abcde config file, open and read contents
7775
abcde_config_path = arm_config["ABCDE_CONFIG_FILE"]

arm/ui/settings/settings.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ def mask_last(value, n=4):
5656
return value[:-n] + '*' * n if len(value) > n else '*' * len(value)
5757

5858

59+
def is_read_only(path: os.PathLike) -> bool:
60+
return not os.access(path, os.W_OK)
61+
62+
5963
route_settings.add_app_template_filter(mask_last, name='mask_last')
6064

6165

@@ -134,7 +138,10 @@ def settings():
134138
arm_path=arm_path,
135139
media_path=media_path,
136140
drives=drives,
137-
form_drive=form_drive)
141+
form_drive=form_drive,
142+
ripper_read_only=is_read_only(cfg.arm_config_path),
143+
apprise_read_only=is_read_only(cfg.apprise_config_path),
144+
abcde_read_only=is_read_only(cfg.abcde_config_path))
138145

139146

140147
def check_hw_transcode_support():
@@ -185,16 +192,19 @@ def save_settings():
185192
# Build the new arm.yaml with updated values from the user
186193
arm_cfg = ui_utils.build_arm_cfg(request.form.to_dict(), comments)
187194
# Save updated arm.yaml
188-
with open(cfg.arm_config_path, "w") as settings_file:
189-
settings_file.write(arm_cfg)
190-
settings_file.close()
191-
success = True
192-
importlib.reload(cfg)
193-
# Set the ARM Log level to the config
194-
app.logger.info(f"Setting log level to: {cfg.arm_config['LOGLEVEL']}")
195-
app.logger.setLevel(cfg.arm_config['LOGLEVEL'])
195+
try:
196+
with open(cfg.arm_config_path, "w") as settings_file:
197+
settings_file.write(arm_cfg)
198+
settings_file.close()
199+
success = True
200+
importlib.reload(cfg)
201+
# Set the ARM Log level to the config
202+
app.logger.info(f"Setting log level to: {cfg.arm_config['LOGLEVEL']}")
203+
app.logger.setLevel(cfg.arm_config['LOGLEVEL'])
204+
except OSError as e:
205+
# arm.yaml is read-only
206+
app.logger.error(f"{cfg.arm_config_path} is read-only", exc_info=e)
196207

197-
# If we get to here there was no post data
198208
return {'success': success, 'settings': cfg.arm_config, 'form': 'arm ripper settings'}
199209

200210

@@ -246,12 +256,15 @@ def save_abcde():
246256
# Windows machines can put \r\n instead of \n newlines, which corrupts the config file
247257
clean_abcde_str = '\n'.join(abcde_cfg_str.splitlines())
248258
# Save updated abcde.conf
249-
with open(cfg.abcde_config_path, "w") as abcde_file:
250-
abcde_file.write(clean_abcde_str)
251-
abcde_file.close()
252-
success = True
253-
# Update the abcde config
254-
cfg.abcde_config = clean_abcde_str
259+
try:
260+
with open(cfg.abcde_config_path, "w") as abcde_file:
261+
abcde_file.write(clean_abcde_str)
262+
abcde_file.close()
263+
success = True
264+
# Update the abcde config
265+
cfg.abcde_config = clean_abcde_str
266+
except OSError as e:
267+
app.logger.error(f"{cfg.abcde_config_path} is read-only", exc_info=e)
255268

256269
# If we get to here, there was no post-data
257270
return {'success': success,
@@ -273,11 +286,14 @@ def save_apprise_cfg():
273286
# Save updated apprise.yaml
274287
# Build the new arm.yaml with updated values from the user
275288
apprise_cfg = ui_utils.build_apprise_cfg(request.form.to_dict())
276-
with open(cfg.apprise_config_path, "w") as settings_file:
277-
settings_file.write(apprise_cfg)
278-
settings_file.close()
279-
success = True
280-
importlib.reload(cfg)
289+
try:
290+
with open(cfg.apprise_config_path, "w") as settings_file:
291+
settings_file.write(apprise_cfg)
292+
settings_file.close()
293+
success = True
294+
importlib.reload(cfg)
295+
except OSError as e:
296+
app.logger.error(f"{cfg.apprise_config_path} is read-only", exc_info=e)
281297
# If we get to here there was no post data
282298
return {'success': success, 'settings': cfg.apprise_config, 'form': 'Apprise config'}
283299

arm/ui/settings/templates/settings/abcde.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ <h4> Music Config </h4>
77
{{ form.hidden_tag() }}
88
<label for="abcdeConfigText">ABCDE Config:</label>
99
<textarea id="abcdeConfigText" name="abcdeConfig" spellcheck="false"
10-
class="w-100 form-control min-vh-100">{{ abcde_cfg }}</textarea>
10+
class="w-100 form-control min-vh-100"
11+
{% if abcde_read_only %}
12+
disabled="" title="abcde.conf is read-only"
13+
{% endif %}
14+
>
15+
{{ abcde_cfg }}
16+
</textarea>
1117
<br>
1218
<button id="abcdeConfigSubmit" class="btn btn-secondary btn-lg btn-block"
1319
form="abcdeSettings">Submit

arm/ui/settings/templates/settings/apprise.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ <h4> Apprise Notification Service </h4>
1111
<span class="input-group-text" id="{{ k }}">{{ k }}: </span>
1212
</div>
1313
<input type="text" class="form-control" aria-label="{{ k }}" name="{{ k }}" placeholder="{{ v }}"
14-
value="{{ v }}" aria-describedby="{{ k }}">
14+
value="{{ v }}" aria-describedby="{{ k }}"
15+
{% if apprise_read_only %}
16+
disabled="" title="apprise.yaml is read-only"
17+
{% endif %}>
1518
</div>
1619
{% endfor %}
1720
<button id="apprise_cfg" class="btn btn-secondary btn-lg btn-block" form="appriseCfg"

arm/ui/settings/templates/settings/ripper.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ <h4> Ripper ARM Settings </h4>
99
<span class="input-group-text" id="{{ k }}">{{ k }}: </span>
1010
</div>
1111
<input type="text" class="form-control" aria-label="{{ k }}" name="{{ k }}"
12-
placeholder="{{ v }}" value="{{ v }}" aria-describedby="{{ k }}">
12+
placeholder="{{ v }}" value="{{ v }}" aria-describedby="{{ k }}"
13+
{% if ripper_read_only %}
14+
disabled="" title="arm.yaml is read-only"
15+
{% endif %}>
1316
<a class="popovers" onClick='return false;' href=""
1417
data-content="{{ jsoncomments[k]| replace("#", "\n") }}" rel="popover"
1518
data-placement="top" data-original-title="{{ k }}">

0 commit comments

Comments
 (0)