mirror of
https://github.com/fergalmoran/picard.git
synced 2026-01-29 03:43:59 +00:00
Merge pull request #2087 from rdswift/backup_and_restore_ini_file
PICARD-2282: Add maintenance options to backup and restore configuration INI files
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
# Copyright (C) 2017 Sophist-UK
|
||||
# Copyright (C) 2018 Vishal Choudhary
|
||||
# Copyright (C) 2020-2021 Gabriel Ferreira
|
||||
# Copyright (C) 2021 Bob Swift
|
||||
# Copyright (C) 2021-2022 Bob Swift
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -241,7 +241,8 @@ class Config(QtCore.QSettings):
|
||||
self.setting = SettingConfigSection(self, "setting")
|
||||
self.persist = ConfigSection(self, "persist")
|
||||
|
||||
TextOption("application", "version", '0.0.0dev0')
|
||||
if 'version' not in self.application or not self.application['version']:
|
||||
TextOption("application", "version", '0.0.0dev0')
|
||||
self._version = Version.from_string(self.application["version"])
|
||||
self._upgrade_hooks = dict()
|
||||
|
||||
@@ -368,11 +369,16 @@ class Config(QtCore.QSettings):
|
||||
def _backup_settings(self):
|
||||
if Version(0, 0, 0) < self._version < PICARD_VERSION:
|
||||
backup_path = self._versioned_config_filename()
|
||||
log.info('Backing up config file to %s', backup_path)
|
||||
try:
|
||||
shutil.copyfile(self.fileName(), backup_path)
|
||||
except OSError:
|
||||
log.error('Failed backing up config file to %s', backup_path)
|
||||
self._save_backup(backup_path)
|
||||
|
||||
def _save_backup(self, backup_path):
|
||||
log.info('Backing up config file to %s', backup_path)
|
||||
try:
|
||||
shutil.copyfile(self.fileName(), backup_path)
|
||||
except OSError:
|
||||
log.error('Failed backing up config file to %s', backup_path)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _write_version(self):
|
||||
self.application["version"] = self._version.to_string()
|
||||
@@ -384,6 +390,12 @@ class Config(QtCore.QSettings):
|
||||
return os.path.join(os.path.dirname(self.fileName()), '%s-%s.ini' % (
|
||||
self.applicationName(), version.to_string(short=True)))
|
||||
|
||||
def save_user_backup(self, backup_path):
|
||||
if backup_path == self.fileName():
|
||||
log.warning("Attempt to backup configuration file to the same path.")
|
||||
return False
|
||||
return self._save_backup(backup_path)
|
||||
|
||||
|
||||
class Option(QtCore.QObject):
|
||||
|
||||
@@ -479,3 +491,14 @@ def get_config():
|
||||
Config objects for threads are created on demand and cached for later use.
|
||||
"""
|
||||
return config
|
||||
|
||||
|
||||
def load_new_config(filename=None):
|
||||
config_file = get_config().fileName()
|
||||
try:
|
||||
shutil.copy(filename, config_file)
|
||||
except OSError:
|
||||
log.error('Failed restoring config file from %s', filename)
|
||||
return False
|
||||
setup_config(QtCore.QObject.tagger, config_file)
|
||||
return True
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# Copyright (C) 2016-2017 Sambhav Kothari
|
||||
# Copyright (C) 2017 Suhas
|
||||
# Copyright (C) 2018 Vishal Choudhary
|
||||
# Copyright (C) 2021 Bob Swift
|
||||
# Copyright (C) 2021-2022 Bob Swift
|
||||
# Copyright (C) 2021 Gabriel Ferreira
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
@@ -174,17 +174,15 @@ class OptionsDialog(PicardDialog, SingletonDialog):
|
||||
self.restoreWindowState()
|
||||
self.finished.connect(self.saveWindowState)
|
||||
|
||||
for page in self.pages:
|
||||
try:
|
||||
page.load()
|
||||
except Exception:
|
||||
log.exception('Failed loading options page %r', page)
|
||||
self.disable_page(page.NAME)
|
||||
self.load_all_pages()
|
||||
self.ui.pages_tree.setCurrentItem(self.default_item)
|
||||
|
||||
self.profile_page = self.get_page('profiles')
|
||||
self.profile_page.signal_refresh.connect(self.update_from_profile_changes)
|
||||
|
||||
self.maintenance_page = self.get_page('maintenance')
|
||||
self.maintenance_page.signal_reload.connect(self.load_all_pages)
|
||||
|
||||
self.first_enter = True
|
||||
self.installEventFilter(self)
|
||||
|
||||
@@ -192,6 +190,14 @@ class OptionsDialog(PicardDialog, SingletonDialog):
|
||||
current_page = self.item_to_page[self.ui.pages_tree.currentItem()]
|
||||
self.set_profiles_button_and_highlight(current_page)
|
||||
|
||||
def load_all_pages(self):
|
||||
for page in self.pages:
|
||||
try:
|
||||
page.load()
|
||||
except Exception:
|
||||
log.exception('Failed loading options page %r', page)
|
||||
self.disable_page(page.NAME)
|
||||
|
||||
def page_has_profile_options(self, page):
|
||||
try:
|
||||
name = page.PARENT if page.PARENT in UserProfileGroups.SETTINGS_GROUPS else page.NAME
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Picard, the next-generation MusicBrainz tagger
|
||||
#
|
||||
# Copyright (C) 2021 Bob Swift
|
||||
# Copyright (C) 2021-2022 Bob Swift
|
||||
# Copyright (C) 2021 Laurent Monin
|
||||
# Copyright (C) 2021-2022 Philipp Wolfer
|
||||
#
|
||||
@@ -21,6 +21,7 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
import datetime
|
||||
from os import path
|
||||
|
||||
from PyQt5 import (
|
||||
@@ -33,7 +34,9 @@ from picard import log
|
||||
from picard.config import (
|
||||
Option,
|
||||
get_config,
|
||||
load_new_config,
|
||||
)
|
||||
from picard.config_upgrade import upgrade_config
|
||||
|
||||
from picard.ui.options import (
|
||||
OptionsPage,
|
||||
@@ -65,6 +68,8 @@ class MaintenanceOptionsPage(OptionsPage):
|
||||
|
||||
options = []
|
||||
|
||||
signal_reload = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.ui = Ui_MaintenanceOptionsPage()
|
||||
@@ -88,6 +93,8 @@ class MaintenanceOptionsPage(OptionsPage):
|
||||
self.ui.select_all.stateChanged.connect(self.select_all_changed)
|
||||
self.ui.enable_cleanup.stateChanged.connect(self.enable_cleanup_changed)
|
||||
self.ui.open_folder_button.clicked.connect(self.open_config_dir)
|
||||
self.ui.save_backup_button.clicked.connect(self.save_backup)
|
||||
self.ui.load_backup_button.clicked.connect(self.load_backup)
|
||||
|
||||
# Set the palette of the config file QLineEdit widget to inactive.
|
||||
palette_normal = self.ui.config_file.palette()
|
||||
@@ -154,6 +161,116 @@ class MaintenanceOptionsPage(OptionsPage):
|
||||
config_dir = path.split(config.fileName())[0]
|
||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(config_dir))
|
||||
|
||||
def _get_dialog_filetypes(self, _ext='.ini'):
|
||||
return ";;".join((
|
||||
_("Configuration files") + " (*{0})".format(_ext,),
|
||||
_("All files") + " (*)",
|
||||
))
|
||||
|
||||
def _make_backup_filename(self, auto=False):
|
||||
config = get_config()
|
||||
_filename = path.split(config.fileName())[1]
|
||||
_root, _ext = path.splitext(_filename)
|
||||
return "{0}_{1}_Backup_{2}{3}".format(
|
||||
_root,
|
||||
'Auto' if auto else 'User',
|
||||
datetime.datetime.now().strftime("%Y%m%d_%H%M"),
|
||||
_ext,
|
||||
)
|
||||
|
||||
def _backup_error(self, dialog_title=None):
|
||||
if not dialog_title:
|
||||
dialog_title = _("Backup Configuration File")
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Icon.Critical,
|
||||
dialog_title,
|
||||
_("There was a problem backing up the configuration file. Please see the logs for more details."),
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
self
|
||||
)
|
||||
dialog.exec_()
|
||||
|
||||
def save_backup(self):
|
||||
config = get_config()
|
||||
directory = path.normpath(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation))
|
||||
filename = self._make_backup_filename()
|
||||
ext = path.splitext(filename)[1]
|
||||
default_path = path.normpath(path.join(directory, filename))
|
||||
|
||||
dialog_title = _("Backup Configuration File")
|
||||
dialog_file_types = self._get_dialog_filetypes(ext)
|
||||
options = QtWidgets.QFileDialog.Options()
|
||||
filename, file_type = QtWidgets.QFileDialog.getSaveFileName(self, dialog_title, default_path, dialog_file_types, options=options)
|
||||
if not filename:
|
||||
return
|
||||
# Fix issue where Qt may set the extension twice
|
||||
(name, ext) = path.splitext(filename)
|
||||
if ext and str(name).endswith('.' + ext):
|
||||
filename = name
|
||||
|
||||
if config.save_user_backup(filename):
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Icon.Information,
|
||||
dialog_title,
|
||||
_("Configuration successfully backed up to %s") % filename,
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
self
|
||||
)
|
||||
dialog.exec_()
|
||||
else:
|
||||
self._backup_error(dialog_title)
|
||||
|
||||
def load_backup(self):
|
||||
dialog_title = _("Load Backup Configuration File")
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Icon.Warning,
|
||||
dialog_title,
|
||||
_("Loading a backup configuration file will replace the current configuration settings. "
|
||||
"A backup copy of the current file will be saved automatically.\n\nDo you want to continue?"),
|
||||
QtWidgets.QMessageBox.StandardButton.Ok | QtWidgets.QMessageBox.StandardButton.Cancel,
|
||||
self
|
||||
)
|
||||
dialog.setDefaultButton(QtWidgets.QMessageBox.StandardButton.Cancel)
|
||||
if not dialog.exec_() == QtWidgets.QMessageBox.StandardButton.Ok:
|
||||
return
|
||||
|
||||
config = get_config()
|
||||
directory = path.normpath(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation))
|
||||
filename = path.join(directory, self._make_backup_filename(auto=True))
|
||||
if not config.save_user_backup(filename):
|
||||
self._backup_error()
|
||||
return
|
||||
|
||||
ext = path.splitext(filename)[1]
|
||||
dialog_file_types = self._get_dialog_filetypes(ext)
|
||||
directory = path.normpath(QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.StandardLocation.DocumentsLocation))
|
||||
options = QtWidgets.QFileDialog.Options()
|
||||
filename, file_type = QtWidgets.QFileDialog.getOpenFileName(self, dialog_title, directory, dialog_file_types, options=options)
|
||||
if not filename:
|
||||
return
|
||||
log.warning('Loading configuration from %s' % filename)
|
||||
if load_new_config(filename):
|
||||
config = get_config()
|
||||
upgrade_config(config)
|
||||
QtCore.QObject.config = get_config()
|
||||
self.signal_reload.emit()
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Icon.Information,
|
||||
dialog_title,
|
||||
_("Configuration successfully loaded from %s") % filename,
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
self
|
||||
)
|
||||
else:
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Icon.Critical,
|
||||
dialog_title,
|
||||
_("There was a problem restoring the configuration file. Please see the logs for more details."),
|
||||
QtWidgets.QMessageBox.StandardButton.Ok,
|
||||
self
|
||||
)
|
||||
dialog.exec_()
|
||||
|
||||
def column_items(self, column):
|
||||
for idx in range(self.ui.tableWidget.rowCount()):
|
||||
yield self.ui.tableWidget.item(idx, column)
|
||||
|
||||
@@ -27,6 +27,18 @@ class Ui_MaintenanceOptionsPage(object):
|
||||
self.open_folder_button.setObjectName("open_folder_button")
|
||||
self.horizontalLayout_3.addWidget(self.open_folder_button)
|
||||
self.vboxlayout.addLayout(self.horizontalLayout_3)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setContentsMargins(-1, -1, -1, 0)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.horizontalLayout.addItem(spacerItem)
|
||||
self.load_backup_button = QtWidgets.QToolButton(MaintenanceOptionsPage)
|
||||
self.load_backup_button.setObjectName("load_backup_button")
|
||||
self.horizontalLayout.addWidget(self.load_backup_button)
|
||||
self.save_backup_button = QtWidgets.QToolButton(MaintenanceOptionsPage)
|
||||
self.save_backup_button.setObjectName("save_backup_button")
|
||||
self.horizontalLayout.addWidget(self.save_backup_button)
|
||||
self.vboxlayout.addLayout(self.horizontalLayout)
|
||||
self.option_counts = QtWidgets.QLabel(MaintenanceOptionsPage)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -46,8 +58,8 @@ class Ui_MaintenanceOptionsPage(object):
|
||||
self.description.setIndent(0)
|
||||
self.description.setObjectName("description")
|
||||
self.vboxlayout.addWidget(self.description)
|
||||
spacerItem = QtWidgets.QSpacerItem(20, 8, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
|
||||
self.vboxlayout.addItem(spacerItem)
|
||||
spacerItem1 = QtWidgets.QSpacerItem(20, 8, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
|
||||
self.vboxlayout.addItem(spacerItem1)
|
||||
self.line = QtWidgets.QFrame(MaintenanceOptionsPage)
|
||||
self.line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
@@ -73,5 +85,7 @@ class Ui_MaintenanceOptionsPage(object):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
self.label.setText(_("Configuration File:"))
|
||||
self.open_folder_button.setText(_("Open folder"))
|
||||
self.load_backup_button.setText(_("Load Backup"))
|
||||
self.save_backup_button.setText(_("Save Backup"))
|
||||
self.enable_cleanup.setText(_("Remove selected options"))
|
||||
self.select_all.setText(_("Select all"))
|
||||
|
||||
@@ -39,6 +39,40 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="load_backup_button">
|
||||
<property name="text">
|
||||
<string>Load Backup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="save_backup_button">
|
||||
<property name="text">
|
||||
<string>Save Backup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="option_counts">
|
||||
<property name="sizePolicy">
|
||||
|
||||
Reference in New Issue
Block a user