diff --git a/picard/__init__.py b/picard/__init__.py index a99c439046..eb50228dc6 100644 --- a/picard/__init__.py +++ b/picard/__init__.py @@ -41,7 +41,7 @@ PICARD_DISPLAY_NAME = "MusicBrainz Picard" PICARD_APP_ID = "org.musicbrainz.Picard" PICARD_DESKTOP_NAME = PICARD_APP_ID + ".desktop" -PICARD_VERSION = Version(3, 0, 0, 'dev', 4) +PICARD_VERSION = Version(3, 0, 0, 'dev', 5) # optional build version diff --git a/picard/config_upgrade.py b/picard/config_upgrade.py index ee0b7685ce..8627b99d3a 100644 --- a/picard/config_upgrade.py +++ b/picard/config_upgrade.py @@ -31,6 +31,7 @@ getmembers, isfunction, ) +import os import re import sys @@ -47,6 +48,7 @@ ) from picard.const.defaults import ( DEFAULT_FILE_NAMING_FORMAT, + DEFAULT_REPLACEMENT, DEFAULT_SCRIPT_NAME, ) from picard.const.sys import IS_FROZEN @@ -550,6 +552,15 @@ def upgrade_to_v3_0_0dev4(config): config.persist.remove('file_view_header_state') +def upgrade_to_v3_0_0dev5(config): + """Ensure "replace_dir_separator" contains no directory separator""" + replace_dir_separator = config.setting['replace_dir_separator'] + replace_dir_separator = replace_dir_separator.replace(os.sep, DEFAULT_REPLACEMENT) + if os.altsep: + replace_dir_separator = replace_dir_separator.replace(os.altsep, DEFAULT_REPLACEMENT) + config.setting['replace_dir_separator'] = replace_dir_separator + + def rename_option(config, old_opt, new_opt, option_type, default): _s = config.setting if old_opt in _s: diff --git a/picard/ui/options/renaming_compat.py b/picard/ui/options/renaming_compat.py index d204c2a044..9a386b3b7c 100644 --- a/picard/ui/options/renaming_compat.py +++ b/picard/ui/options/renaming_compat.py @@ -4,7 +4,7 @@ # # Copyright (C) 2006-2008, 2011 Lukáš Lalinský # Copyright (C) 2008-2009 Nikolai Prokoschenko -# Copyright (C) 2009-2010, 2014-2015, 2018-2022 Philipp Wolfer +# Copyright (C) 2009-2010, 2014-2015, 2018-2024 Philipp Wolfer # Copyright (C) 2011-2013 Michael Wiencek # Copyright (C) 2011-2013 Wieland Hoffmann # Copyright (C) 2013 Calvin Walton @@ -32,7 +32,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - +import os import re from PyQt6 import ( @@ -56,7 +56,10 @@ Ui_RenamingCompatOptionsPage, ) from picard.ui.forms.ui_win_compat_dialog import Ui_WinCompatDialog -from picard.ui.options import OptionsPage +from picard.ui.options import ( + OptionsCheckError, + OptionsPage, +) class RenamingCompatOptionsPage(OptionsPage): @@ -80,6 +83,7 @@ def __init__(self, parent=None): self.ui.windows_long_paths.toggled.connect(self.on_options_changed) self.ui.replace_spaces_with_underscores.toggled.connect(self.on_options_changed) self.ui.replace_dir_separator.textChanged.connect(self.on_options_changed) + self.ui.replace_dir_separator.setValidator(NoDirectorySeparatorValidator()) self.ui.btn_windows_compatibility_change.clicked.connect(self.open_win_compat_dialog) self.register_setting('ascii_filenames', ['ascii_filenames']) @@ -113,6 +117,14 @@ def save(self): for key, value in options.items(): config.setting[key] = value + def check(self): + (valid_state, _text, _pos) = self.ui.replace_dir_separator.validator().validate(self.ui.replace_dir_separator.text(), 0) + if valid_state != QtGui.QValidator.State.Acceptable: + raise OptionsCheckError( + _("Invalid directory separator replacement"), + _("The replacement for directory separators must not be itself a directory separator.") + ) + def toggle_windows_long_paths(self, state): if state and not system_supports_long_paths(): dialog = QtWidgets.QMessageBox( @@ -147,6 +159,15 @@ def open_win_compat_dialog(self): self.on_options_changed() +class NoDirectorySeparatorValidator(QtGui.QValidator): + def validate(self, text: str, pos): + if os.sep in text or (os.altsep and os.altsep in text): + state = QtGui.QValidator.State.Invalid + else: + state = QtGui.QValidator.State.Acceptable + return state, text, pos + + class WinCompatReplacementValidator(QtGui.QValidator): _re_valid_win_replacement = re.compile(r'^[^"*:<>?|/\\\s]?$') diff --git a/test/test_config_upgrade.py b/test/test_config_upgrade.py index 66e7e8330c..3078f64ae9 100644 --- a/test/test_config_upgrade.py +++ b/test/test_config_upgrade.py @@ -21,6 +21,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +import os from PyQt6.QtCore import QByteArray @@ -66,9 +67,11 @@ upgrade_to_v2_8_0dev2, upgrade_to_v3_0_0dev3, upgrade_to_v3_0_0dev4, + upgrade_to_v3_0_0dev5, ) from picard.const.defaults import ( DEFAULT_FILE_NAMING_FORMAT, + DEFAULT_REPLACEMENT, DEFAULT_SCRIPT_NAME, ) from picard.util import unique_numbered_title @@ -541,3 +544,14 @@ def test_upgrade_to_v3_0_0dev4(self): upgrade_to_v3_0_0dev4(self.config) self.assertEqual(b'', self.config.persist['album_view_header_state']) self.assertEqual(b'', self.config.persist['file_view_header_state']) + + def test_upgrade_to_v3_0_0dev5(self): + TextOption('setting', 'replace_dir_separator', DEFAULT_REPLACEMENT) + self.config.setting['replace_dir_separator'] = os.sep + upgrade_to_v3_0_0dev5(self.config) + self.assertEqual(DEFAULT_REPLACEMENT, self.config.setting['replace_dir_separator']) + + if os.altsep: + self.config.setting['replace_dir_separator'] = os.altsep + upgrade_to_v3_0_0dev5(self.config) + self.assertEqual(DEFAULT_REPLACEMENT, self.config.setting['replace_dir_separator'])