Settingsaccessor: Extract file access and error reporting

Move file access and error reporting out of SettingsAccessor and into
BasicSettingsAccessor.

Change-Id: If6cc9157c1a532f117c48b929c05f55d89a339eb
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Tobias Hunger
2017-12-11 11:30:01 +01:00
parent f33ef0e80b
commit 21e7e9ea3b
6 changed files with 387 additions and 344 deletions

View File

@@ -25,6 +25,7 @@
#include "settingsaccessor.h"
#include "asconst.h"
#include "qtcassert.h"
#include <QApplication>
@@ -150,99 +151,158 @@ void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
namespace Utils {
// --------------------------------------------------------------------
// BasicSettingsAccessor::Issue:
// --------------------------------------------------------------------
QMessageBox::StandardButtons BasicSettingsAccessor::Issue::allButtons() const
{
QMessageBox::StandardButtons result = QMessageBox::NoButton;
for (const QMessageBox::StandardButton &b : buttons.keys())
result |= b;
return result;
}
// --------------------------------------------------------------------
// BasicSettingsAccessor:
// --------------------------------------------------------------------
BasicSettingsAccessor::BasicSettingsAccessor(const FileName &baseFilePath, const QString &docType) :
m_baseFilePath(baseFilePath),
m_docType(docType)
/*!
* The BasicSettingsAccessor can be used to read/write settings in XML format.
*/
BasicSettingsAccessor::BasicSettingsAccessor(const QString &docType,
const QString &displayName,
const QString &applicationDisplayName) :
docType(docType),
displayName(displayName),
applicationDisplayName(applicationDisplayName)
{
QTC_CHECK(!m_baseFilePath.isEmpty());
QTC_CHECK(!m_docType.isEmpty());
}
BasicSettingsAccessor::~BasicSettingsAccessor() = default;
QVariantMap BasicSettingsAccessor::restoreSettings(QWidget *parent) const
{
Q_UNUSED(parent);
return readFile(m_baseFilePath);
}
bool BasicSettingsAccessor::saveSettings(const QVariantMap &data, QWidget *parent) const
{
return writeFile(m_baseFilePath, data, parent);
}
QVariantMap BasicSettingsAccessor::readFile(const FileName &path) const
{
PersistentSettingsReader reader;
if (!reader.load(path))
return QVariantMap();
return reader.restoreValues();
}
bool BasicSettingsAccessor::writeFile(const FileName &path, const QVariantMap &data, QWidget *parent) const
{
if (data.isEmpty())
return false;
if (!m_writer || m_writer->fileName() != path)
m_writer = std::make_unique<PersistentSettingsWriter>(path, m_docType);
return m_writer->save(data, parent);
}
FileName BasicSettingsAccessor::baseFilePath() const
{
return m_baseFilePath;
}
// -----------------------------------------------------------------------------
// VersionUpgrader:
// -----------------------------------------------------------------------------
VersionUpgrader::VersionUpgrader(const int version, const QString &extension) :
m_version(version), m_extension(extension)
{ }
int VersionUpgrader::version() const
{
QTC_CHECK(m_version >= 0);
return m_version;
}
QString VersionUpgrader::backupExtension() const
{
QTC_CHECK(!m_extension.isEmpty());
return m_extension;
QTC_CHECK(!docType.isEmpty());
QTC_CHECK(!displayName.isEmpty());
QTC_CHECK(!applicationDisplayName.isEmpty());
}
/*!
* Performs a simple renaming of the listed keys in \a changes recursively on \a map.
* Restore settings from disk and report any issues in a message box centered on \a parent.
*/
QVariantMap VersionUpgrader::renameKeys(const QList<Change> &changes, QVariantMap map) const
QVariantMap BasicSettingsAccessor::restoreSettings(QWidget *parent) const
{
foreach (const Change &change, changes) {
QVariantMap::iterator oldSetting = map.find(change.first);
if (oldSetting != map.end()) {
map.insert(change.second, oldSetting.value());
map.erase(oldSetting);
}
QTC_ASSERT(!m_baseFilePath.isEmpty(), return QVariantMap());
const RestoreData result = readData(m_baseFilePath, parent);
const ProceedInfo pi = result.issue ? reportIssues(result.issue.value(), result.path, parent) : ProceedInfo::Continue;
return pi == ProceedInfo::DiscardAndContinue ? QVariantMap() : result.data;
}
/*!
* Save \a data to disk and report any issues in a message box centered on \a parent.
*/
bool BasicSettingsAccessor::saveSettings(const QVariantMap &data, QWidget *parent) const
{
const optional<Issue> result = writeData(m_baseFilePath, data);
const ProceedInfo pi = result ? reportIssues(result.value(), m_baseFilePath, parent) : ProceedInfo::Continue;
return pi == ProceedInfo::Continue;
}
/*!
* Read data from \a path. Do all the necessary postprocessing of the data.
*/
BasicSettingsAccessor::RestoreData BasicSettingsAccessor::readData(const FileName &path,
QWidget *parent) const
{
Q_UNUSED(parent);
RestoreData result = readFile(path);
if (!result.data.isEmpty())
result.data = preprocessReadSettings(result.data);
return result;
}
/*!
* Store the \a data in \a path on disk. Do all the necessary preprocessing of the data.
*/
Utils::optional<BasicSettingsAccessor::Issue>
BasicSettingsAccessor::writeData(const FileName &path, const QVariantMap &data) const
{
return writeFile(path, prepareToWriteSettings(data));
}
/*!
* Read a file at \a path from disk and extract the data into a RestoreData set.
*
* This method does not do *any* processing of the file contents.
*/
BasicSettingsAccessor::RestoreData BasicSettingsAccessor::readFile(const FileName &path) const
{
PersistentSettingsReader reader;
if (!reader.load(path)) {
return RestoreData(Issue(QCoreApplication::translate("Utils::SettingsAccessor", "Failed to Read File"),
QCoreApplication::translate("Utils::SettingsAccessor", "Could not open \"%1\".")
.arg(path.toUserOutput())));
}
QVariantMap::iterator i = map.begin();
while (i != map.end()) {
QVariant v = i.value();
if (v.type() == QVariant::Map)
i.value() = renameKeys(changes, v.toMap());
return RestoreData(path, reader.restoreValues());
}
++i;
/*!
* Write a file at \a path to disk and store the \a data in it.
*
* This method does not do *any* processing of the file contents.
*/
Utils::optional<BasicSettingsAccessor::Issue>
BasicSettingsAccessor::writeFile(const FileName &path, const QVariantMap &data) const
{
if (data.isEmpty()) {
return Issue(QCoreApplication::translate("Utils::SettingsAccessor", "Failed to Write File"),
QCoreApplication::translate("Utils::SettingsAccessor", "There was nothing to write."));
}
return map;
QString errorMessage;
if (!m_writer || m_writer->fileName() != path)
m_writer = std::make_unique<PersistentSettingsWriter>(path, docType);
if (!m_writer->save(data, &errorMessage)) {
return Issue(QCoreApplication::translate("Utils::SettingsAccessor", "Failed to Write File"),
errorMessage);
}
return {};
}
BasicSettingsAccessor::ProceedInfo
BasicSettingsAccessor::reportIssues(const BasicSettingsAccessor::Issue &issue, const FileName &path,
QWidget *parent) const
{
if (!path.exists())
return Continue;
const QMessageBox::Icon icon
= issue.buttons.count() > 1 ? QMessageBox::Question : QMessageBox::Information;
const QMessageBox::StandardButtons buttons = issue.allButtons();
QTC_ASSERT(buttons != QMessageBox::NoButton, return Continue);
QMessageBox msgBox(icon, issue.title, issue.message, buttons, parent);
if (issue.defaultButton != QMessageBox::NoButton)
msgBox.setDefaultButton(issue.defaultButton);
if (issue.escapeButton != QMessageBox::NoButton)
msgBox.setEscapeButton(issue.escapeButton);
int boxAction = msgBox.exec();
return issue.buttons.value(static_cast<QMessageBox::StandardButton>(boxAction));
}
/*!
* This method is called right after reading data from disk and modifies \a data.
*/
QVariantMap BasicSettingsAccessor::preprocessReadSettings(const QVariantMap &data) const
{
return data;
}
/*!
* This method is called right before writing data to disk and modifies \a data.
*/
QVariantMap BasicSettingsAccessor::prepareToWriteSettings(const QVariantMap &data) const
{
return data;
}
// --------------------------------------------------------------------
@@ -271,16 +331,13 @@ public:
return m_upgraders[static_cast<size_t>(pos)].get();
return nullptr;
}
Settings bestSettings(const SettingsAccessor *accessor, const FileNameList &pathList);
BasicSettingsAccessor::RestoreData bestSettings(const SettingsAccessor *accessor, const FileNameList &pathList, QWidget *parent);
std::vector<std::unique_ptr<VersionUpgrader>> m_upgraders;
std::unique_ptr<PersistentSettingsWriter> m_writer;
QByteArray m_settingsId;
QString m_displayName;
QString m_applicationDisplayName;
QString m_userSuffix;
QString m_sharedSuffix;
std::unique_ptr<BasicSettingsAccessor> m_sharedFile;
};
// Return path to shared directory for .user files, create if necessary.
@@ -355,16 +412,68 @@ static FileName userFilePath(const Utils::FileName &projectFilePath, const QStri
return result;
}
// --------------------------------------------------------------------
// VersionUpgrader:
// --------------------------------------------------------------------
VersionUpgrader::VersionUpgrader(const int version, const QString &extension) :
m_version(version), m_extension(extension)
{
QTC_CHECK(m_version >= 0);
}
int VersionUpgrader::version() const
{
return m_version;
}
QString VersionUpgrader::backupExtension() const
{
QTC_CHECK(!m_extension.isEmpty());
return m_extension;
}
/*!
* Performs a simple renaming of the listed keys in \a changes recursively on \a map.
*/
QVariantMap VersionUpgrader::renameKeys(const QList<Change> &changes, QVariantMap map) const
{
foreach (const Change &change, changes) {
QVariantMap::iterator oldSetting = map.find(change.first);
if (oldSetting != map.end()) {
map.insert(change.second, oldSetting.value());
map.erase(oldSetting);
}
}
QVariantMap::iterator i = map.begin();
while (i != map.end()) {
QVariant v = i.value();
if (v.type() == QVariant::Map)
i.value() = renameKeys(changes, v.toMap());
++i;
}
return map;
}
// -----------------------------------------------------------------------------
// SettingsAccessor:
// -----------------------------------------------------------------------------
SettingsAccessor::SettingsAccessor(const Utils::FileName &baseFile, const QString &docType) :
BasicSettingsAccessor(baseFile, docType),
SettingsAccessor::SettingsAccessor(const Utils::FileName &baseFile, const QString &docType,
const QString &displayName, const QString &appDisplayName) :
BasicSettingsAccessor(docType, displayName, appDisplayName),
d(new SettingsAccessorPrivate)
{
d->m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), ".user");
d->m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), ".shared");
Utils::FileName baseFilePath = userFilePath(baseFile, generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), ".user"));
setBaseFilePath(baseFilePath);
Utils::FileName sharedFilePath = baseFile;
sharedFilePath.appendString(generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), ".shared"));
d->m_sharedFile = std::make_unique<BasicSettingsAccessor>(docType, displayName, appDisplayName);
d->m_sharedFile->setBaseFilePath(sharedFilePath);
}
SettingsAccessor::~SettingsAccessor()
@@ -396,19 +505,6 @@ QVariantMap SettingsAccessor::setVersionInMap(const QVariantMap &data, int versi
return result;
}
/*!
* Run directly after reading the \a data.
*
* This method is called right after reading the data before any attempt at interpreting the data
* is made.
*
* Returns the prepared data.
*/
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
return data;
}
/*!
* Check which of two sets of data are a better match to load.
*
@@ -480,102 +576,56 @@ QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data, int targe
return result;
}
/*!
* Find issues with the settings file and warn the user about them.
*
* \a data is the data from the settings file.
* \a path is the full path to the settings used.
* \a parent is the widget to be set as parent of any dialogs that are opened.
*
* Returns \c true if the settings are not usable anymore and \c false otherwise.
*/
SettingsAccessor::ProceedInfo SettingsAccessor::reportIssues(const QVariantMap &data,
const FileName &path,
QWidget *parent) const
{
if (!path.exists())
return Continue;
const Utils::optional<IssueInfo> issue = findIssues(data, path);
if (!issue)
return Continue;
const IssueInfo &details = issue.value();
const QMessageBox::Icon icon
= details.buttons.count() > 1 ? QMessageBox::Question : QMessageBox::Information;
const QMessageBox::StandardButtons buttons = [&details]()
{
QMessageBox::StandardButtons buttons = QMessageBox::NoButton;
for (const QMessageBox::StandardButton &b : details.buttons.keys())
buttons |= b;
return buttons;
}();
QTC_ASSERT(buttons != QMessageBox::NoButton, return Continue);
QMessageBox msgBox(icon, details.title, details.message, buttons, parent);
if (details.defaultButton != QMessageBox::NoButton)
msgBox.setDefaultButton(details.defaultButton);
if (details.escapeButton != QMessageBox::NoButton)
msgBox.setEscapeButton(details.escapeButton);
int boxAction = msgBox.exec();
return details.buttons.value(static_cast<QMessageBox::StandardButton>(boxAction));
}
/*!
* Checks \a data located at \a path for issues to be displayed with reportIssues.
*
* Returns a IssueInfo object which is then used by reportIssues.
*/
Utils::optional<SettingsAccessor::IssueInfo>
Utils::optional<SettingsAccessor::Issue>
SettingsAccessor::findIssues(const QVariantMap &data, const FileName &path) const
{
const FileName defaultSettingsPath = userFilePath(baseFilePath(), d->m_userSuffix);
const int version = versionFromMap(data);
if (data.isEmpty() || version < firstSupportedVersion() || version > currentVersion()) {
IssueInfo result;
result.title = QApplication::translate("Utils::SettingsAccessor", "No Valid Settings Found");
result.message = QApplication::translate("Utils::SettingsAccessor",
"<p>No valid settings file could be found.</p>"
"<p>All settings files found in directory \"%1\" "
"were either too new or too old to be read.</p>")
.arg(path.toUserOutput());
Issue result(QApplication::translate("Utils::SettingsAccessor", "No Valid Settings Found"),
QApplication::translate("Utils::SettingsAccessor",
"<p>No valid settings file could be found.</p>"
"<p>All settings files found in directory \"%1\" "
"were either too new or too old to be read.</p>")
.arg(path.toUserOutput()));
result.buttons.insert(QMessageBox::Ok, DiscardAndContinue);
return result;
}
if ((path != defaultSettingsPath) && (version < currentVersion())) {
IssueInfo result;
result.title = QApplication::translate("Utils::SettingsAccessor", "Using Old Settings");
result.message = QApplication::translate("Utils::SettingsAccessor",
"<p>The versioned backup \"%1\" of the settings "
"file is used, because the non-versioned file was "
"created by an incompatible version of %2.</p>"
"<p>Settings changes made since the last time this "
"version of %2 was used are ignored, and "
"changes made now will <b>not</b> be propagated to "
"the newer version.</p>").arg(path.toUserOutput())
.arg(d->m_applicationDisplayName);
if ((path != baseFilePath()) && (version < currentVersion())) {
Issue result(QApplication::translate("Utils::SettingsAccessor", "Using Old Settings"),
QApplication::translate("Utils::SettingsAccessor",
"<p>The versioned backup \"%1\" of the settings "
"file is used, because the non-versioned file was "
"created by an incompatible version of %2.</p>"
"<p>Settings changes made since the last time this "
"version of %2 was used are ignored, and "
"changes made now will <b>not</b> be propagated to "
"the newer version.</p>")
.arg(path.toUserOutput())
.arg(applicationDisplayName));
result.buttons.insert(QMessageBox::Ok, Continue);
return result;
}
const QByteArray readId = settingsIdFromMap(data);
if (!readId.isEmpty() && readId != settingsId()) {
IssueInfo result;
result.title = differentEnvironmentMsg(d->m_displayName);
result.message = QApplication::translate("Utils::EnvironmentIdAccessor",
"<p>No .user settings file created by this instance "
"of %1 was found.</p>"
"<p>Did you work with this project on another machine or "
"using a different settings path before?</p>"
"<p>Do you still want to load the settings file \"%2\"?</p>")
.arg(d->m_applicationDisplayName)
.arg(path.toUserOutput());
Issue result(differentEnvironmentMsg(displayName),
QApplication::translate("Utils::EnvironmentIdAccessor",
"<p>No .user settings file created by this instance "
"of %1 was found.</p>"
"<p>Did you work with this project on another machine or "
"using a different settings path before?</p>"
"<p>Do you still want to load the settings file \"%2\"?</p>")
.arg(applicationDisplayName)
.arg(path.toUserOutput()));
result.defaultButton = QMessageBox::No;
result.escapeButton = QMessageBox::No;
result.buttons.insert(QMessageBox::Yes, SettingsAccessor::Continue);
result.buttons.insert(QMessageBox::No, SettingsAccessor::DiscardAndContinue);
result.buttons.insert(QMessageBox::Yes, Continue);
result.buttons.insert(QMessageBox::No, DiscardAndContinue);
return result;
}
@@ -604,16 +654,7 @@ QByteArray SettingsAccessor::settingsIdFromMap(const QVariantMap &data)
return data.value(SETTINGS_ID_KEY).toByteArray();
}
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
{
QTC_ASSERT(d->lastVersion() >= 0, return QVariantMap());
const QVariantMap userSettings = readUserSettings(parent);
const QVariantMap sharedSettings = readSharedSettings(parent);
return mergeSettings(userSettings, sharedSettings);
}
QVariantMap SettingsAccessor::prepareToSaveSettings(const QVariantMap &data) const
QVariantMap SettingsAccessor::prepareToWriteSettings(const QVariantMap &data) const
{
QVariantMap tmp = data;
const QVariant &shared = retrieveSharedSettings();
@@ -626,19 +667,6 @@ QVariantMap SettingsAccessor::prepareToSaveSettings(const QVariantMap &data) con
return tmp;
}
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
{
if (map.isEmpty())
return false;
backupUserFile();
QVariantMap data = prepareToSaveSettings(map);
FileName path = FileName::fromString(defaultFileName(d->m_userSuffix));
return writeFile(path, data, parent);
}
bool SettingsAccessor::addVersionUpgrader(std::unique_ptr<VersionUpgrader> upgrader)
{
QTC_ASSERT(upgrader.get(), return false);
@@ -651,29 +679,51 @@ bool SettingsAccessor::addVersionUpgrader(std::unique_ptr<VersionUpgrader> upgra
return true;
}
BasicSettingsAccessor::RestoreData SettingsAccessor::readData(const FileName &path,
QWidget *parent) const
{
Q_UNUSED(path); // FIXME: This is wrong!
// FIXME: Do better error handling:
QTC_ASSERT(d->lastVersion() >= 0, return RestoreData("SETUP FAILED", "SETUP FAILED"));
RestoreData userSettings = readUserSettings(parent);
if (userSettings.issue && reportIssues(userSettings.issue.value(), userSettings.path, parent) == DiscardAndContinue)
userSettings.data.clear();
RestoreData sharedSettings = readSharedSettings(parent);
if (sharedSettings.issue && reportIssues(sharedSettings.issue.value(), sharedSettings.path, parent) == DiscardAndContinue)
sharedSettings.data.clear();
RestoreData mergedSettings = RestoreData(userSettings.path,
mergeSettings(userSettings.data, sharedSettings.data));
mergedSettings.issue = findIssues(mergedSettings.data, mergedSettings.path);
return mergedSettings;
}
Utils::optional<BasicSettingsAccessor::Issue> SettingsAccessor::writeData(const FileName &path,
const QVariantMap &data) const
{
if (data.isEmpty())
return {};
backupUserFile();
return BasicSettingsAccessor::writeData(path, data);
}
void SettingsAccessor::setSettingsId(const QByteArray &id)
{
d->m_settingsId = id;
}
void SettingsAccessor::setDisplayName(const QString &dn)
{
d->m_displayName = dn;
}
void SettingsAccessor::setApplicationDisplayName(const QString &dn)
{
d->m_applicationDisplayName = dn;
}
/* Will always return the default name first (if applicable) */
FileNameList SettingsAccessor::settingsFiles(const QString &suffix) const
FileNameList SettingsAccessor::settingsFiles() const
{
FileNameList result;
QFileInfoList list;
const QFileInfo pfi = baseFilePath().toFileInfo();
const QStringList filter(pfi.fileName() + suffix + '*');
const QStringList filter(pfi.fileName() + '*');
if (!sharedUserFileDir().isEmpty()) {
const QString sharedPath = sharedUserFileDir() + '/' + makeRelative(pfi.absolutePath());
@@ -684,7 +734,7 @@ FileNameList SettingsAccessor::settingsFiles(const QString &suffix) const
foreach (const QFileInfo &fi, list) {
const FileName path = FileName::fromString(fi.absoluteFilePath());
if (!result.contains(path)) {
if (path.endsWith(suffix))
if (path == baseFilePath())
result.prepend(path);
else
result.append(path);
@@ -699,16 +749,6 @@ QByteArray SettingsAccessor::settingsId() const
return d->m_settingsId;
}
QString SettingsAccessor::displayName() const
{
return d->m_displayName;
}
QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
return userFilePath(baseFilePath(), suffix).toString();
}
int SettingsAccessor::currentVersion() const
{
return d->currentVersion();
@@ -721,7 +761,7 @@ int SettingsAccessor::firstSupportedVersion() const
FileName SettingsAccessor::backupName(const QVariantMap &data) const
{
QString backupName = defaultFileName(d->m_userSuffix);
QString backupName = baseFilePath().toString();
const QByteArray oldEnvironmentId = settingsIdFromMap(data);
if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != settingsId())
backupName += '.' + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
@@ -738,89 +778,71 @@ FileName SettingsAccessor::backupName(const QVariantMap &data) const
void SettingsAccessor::backupUserFile() const
{
SettingsAccessorPrivate::Settings oldSettings;
oldSettings.path = FileName::fromString(defaultFileName(d->m_userSuffix));
oldSettings.map = readFile(oldSettings.path);
if (oldSettings.map.isEmpty())
RestoreData oldSettings = BasicSettingsAccessor::readFile(baseFilePath());
if (oldSettings.data.isEmpty())
return;
// Do we need to do a backup?
const QString origName = oldSettings.path.toString();
QString backupFileName = backupName(oldSettings.map).toString();
QString backupFileName = backupName(oldSettings.data).toString();
if (backupFileName != origName)
QFile::copy(origName, backupFileName);
}
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
SettingsAccessor::RestoreData SettingsAccessor::readUserSettings(QWidget *parent) const
{
SettingsAccessorPrivate::Settings result;
FileNameList fileList = settingsFiles(d->m_userSuffix);
FileNameList fileList = settingsFiles();
if (fileList.isEmpty()) // No settings found at all.
return result.map;
return RestoreData(baseFilePath(), QVariantMap());
result = d->bestSettings(this, fileList);
RestoreData result = d->bestSettings(this, fileList, parent);
if (result.path.isEmpty())
result.path = baseFilePath().parentDir();
ProceedInfo proceed = reportIssues(result.map, result.path, parent);
if (proceed == DiscardAndContinue)
return QVariantMap();
return result.map;
return result;
}
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
SettingsAccessor::RestoreData SettingsAccessor::readSharedSettings(QWidget *parent) const
{
SettingsAccessorPrivate::Settings sharedSettings;
QString fn = baseFilePath().toString() + d->m_sharedSuffix;
sharedSettings.path = FileName::fromString(fn);
sharedSettings.map = readFile(sharedSettings.path);
RestoreData sharedSettings = d->m_sharedFile->readData(d->m_sharedFile->baseFilePath(), parent);
if (versionFromMap(sharedSettings.map) > currentVersion()) {
if (versionFromMap(sharedSettings.data) > currentVersion()) {
// The shared file version is newer than Creator... If we have valid user
// settings we prompt the user whether we could try an *unsupported* update.
// This makes sense since the merging operation will only replace shared settings
// that perfectly match corresponding user ones. If we don't have valid user
// settings to compare against, there's nothing we can do.
QMessageBox msgBox(
QMessageBox::Question,
QApplication::translate("Utils::SettingsAccessor",
"Unsupported Shared Settings File"),
QApplication::translate("Utils::SettingsAccessor",
"The version of your .shared file is not "
"supported by %1. "
"Do you want to try loading it anyway?")
.arg(d->m_applicationDisplayName),
QMessageBox::Yes | QMessageBox::No,
parent);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setEscapeButton(QMessageBox::No);
if (msgBox.exec() == QMessageBox::No)
sharedSettings.map.clear();
else
sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
sharedSettings.issue = Issue(QApplication::translate("Utils::SettingsAccessor",
"Unsupported Shared Settings File"),
QApplication::translate("Utils::SettingsAccessor",
"The version of your .shared file is not "
"supported by %1. "
"Do you want to try loading it anyway?"));
sharedSettings.issue->buttons.insert(QMessageBox::Yes, Continue);
sharedSettings.issue->buttons.insert(QMessageBox::No, DiscardAndContinue);
sharedSettings.issue->defaultButton = QMessageBox::No;
sharedSettings.issue->escapeButton = QMessageBox::No;
}
return sharedSettings.map;
return sharedSettings;
}
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor,
const FileNameList &pathList)
SettingsAccessor::RestoreData SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor,
const FileNameList &pathList,
QWidget *parent)
{
Settings bestMatch;
SettingsAccessor::RestoreData bestMatch;
foreach (const FileName &path, pathList) {
const QVariantMap tmp = accessor->prepareSettings(accessor->readFile(path));
const SettingsAccessor::RestoreData tmp = accessor->BasicSettingsAccessor::readData(path, parent);
if (accessor->isBetterMatch(bestMatch.map, tmp)) {
bestMatch.path = path;
bestMatch.map = tmp;
}
if (accessor->isBetterMatch(bestMatch.data, tmp.data))
bestMatch = tmp;
}
return bestMatch;
}
QVariantMap SettingsAccessor::mergeSettings(const QVariantMap &userMap,
const QVariantMap &sharedMap) const
QVariantMap
SettingsAccessor::mergeSettings(const QVariantMap &userMap, const QVariantMap &sharedMap) const
{
QVariantMap newUser = userMap;
QVariantMap newShared = sharedMap;

View File

@@ -35,30 +35,73 @@
#include <QMessageBox>
#include <QVariantMap>
#include <memory>
namespace Utils {
// --------------------------------------------------------------------
// BasicSettingsAccessor:
// --------------------------------------------------------------------
// Read/write files incl. error handling suitable for the UI:
class QTCREATOR_UTILS_EXPORT BasicSettingsAccessor
{
public:
BasicSettingsAccessor(const Utils::FileName &baseFilePath, const QString &docType);
virtual ~BasicSettingsAccessor();
BasicSettingsAccessor(const QString &docType, const QString &displayName,
const QString &applicationDisplayName);
virtual ~BasicSettingsAccessor() = default;
virtual QVariantMap restoreSettings(QWidget *parent) const;
virtual bool saveSettings(const QVariantMap &data, QWidget *parent) const;
enum ProceedInfo { Continue, DiscardAndContinue };
typedef QHash<QMessageBox::StandardButton, ProceedInfo> ButtonMap;
class Issue {
public:
Issue(const QString &title, const QString &message) : title{title}, message{message} { }
QMessageBox::StandardButtons allButtons() const;
QString title;
QString message;
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton;
QMessageBox::StandardButton escapeButton = QMessageBox::Ok;
QHash<QMessageBox::StandardButton, ProceedInfo> buttons = {{QMessageBox::Ok, ProceedInfo::Continue}};
};
class RestoreData {
public:
RestoreData() = default;
RestoreData(const Utils::FileName &path, const QVariantMap &data) : path{path}, data{data} { }
RestoreData(const QString &title, const QString &message) : RestoreData(Issue(title, message)) { }
RestoreData(const Issue &issue) : issue{issue} { }
Utils::FileName path;
QVariantMap data;
Utils::optional<Issue> issue;
};
QVariantMap restoreSettings(QWidget *parent) const;
bool saveSettings(const QVariantMap &data, QWidget *parent) const;
const QString docType;
const QString displayName;
const QString applicationDisplayName;
void setBaseFilePath(const Utils::FileName &baseFilePath) { m_baseFilePath = baseFilePath; }
Utils::FileName baseFilePath() const { return m_baseFilePath; }
virtual RestoreData readData(const Utils::FileName &path, QWidget *parent) const;
virtual Utils::optional<Issue> writeData(const Utils::FileName &path, const QVariantMap &data) const;
protected:
QVariantMap readFile(const Utils::FileName &path) const;
bool writeFile(const Utils::FileName &path, const QVariantMap &data, QWidget *parent) const;
// Report errors:
ProceedInfo reportIssues(const Issue &issue, const FileName &path, QWidget *parent) const;
Utils::FileName baseFilePath() const;
virtual QVariantMap preprocessReadSettings(const QVariantMap &data) const;
virtual QVariantMap prepareToWriteSettings(const QVariantMap &data) const;
RestoreData readFile(const Utils::FileName &path) const;
Utils::optional<Issue> writeFile(const Utils::FileName &path, const QVariantMap &data) const;
private:
const Utils::FileName m_baseFilePath;
const QString m_docType;
Utils::FileName m_baseFilePath;
mutable std::unique_ptr<PersistentSettingsWriter> m_writer;
};
@@ -92,12 +135,10 @@ class SettingsAccessorPrivate;
class QTCREATOR_UTILS_EXPORT SettingsAccessor : public BasicSettingsAccessor
{
public:
explicit SettingsAccessor(const Utils::FileName &baseFile, const QString &docType);
explicit SettingsAccessor(const Utils::FileName &baseFile, const QString &docType,
const QString &displayName, const QString &appDisplayName);
~SettingsAccessor() override;
QVariantMap restoreSettings(QWidget *parent) const override;
bool saveSettings(const QVariantMap &data, QWidget *parent) const override;
static QVariantMap setVersionInMap(const QVariantMap &data, int version);
static int versionFromMap(const QVariantMap &data);
static int originalVersionFromMap(const QVariantMap &data);
@@ -108,51 +149,35 @@ public:
bool addVersionUpgrader(std::unique_ptr<VersionUpgrader> upgrader);
enum ProceedInfo { Continue, DiscardAndContinue };
typedef QHash<QMessageBox::StandardButton, ProceedInfo> ButtonMap;
class IssueInfo {
public:
QString title;
QString message;
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton;
QMessageBox::StandardButton escapeButton = QMessageBox::NoButton;
QHash<QMessageBox::StandardButton, ProceedInfo> buttons;
};
protected:
RestoreData readData(const Utils::FileName &path, QWidget *parent) const final;
Utils::optional<Issue> writeData(const Utils::FileName &path, const QVariantMap &data) const final;
void setSettingsId(const QByteArray &id);
void setDisplayName(const QString &dn);
void setApplicationDisplayName(const QString &dn);
QVariantMap upgradeSettings(const QVariantMap &data) const;
QVariantMap upgradeSettings(const QVariantMap &data, const int targetVersion) const;
ProceedInfo reportIssues(const QVariantMap &data, const Utils::FileName &path, QWidget *parent) const;
virtual QVariantMap prepareSettings(const QVariantMap &data) const;
virtual QVariantMap prepareToSaveSettings(const QVariantMap &data) const;
QVariantMap prepareToWriteSettings(const QVariantMap &data) const override;
virtual bool isBetterMatch(const QVariantMap &origData, const QVariantMap &newData) const;
virtual bool isValidVersionAndId(const int version, const QByteArray &id) const;
virtual Utils::FileName backupName(const QVariantMap &data) const;
virtual Utils::optional<IssueInfo> findIssues(const QVariantMap &data,
const Utils::FileName &path) const;
QVariantMap mergeSettings(const QVariantMap &userMap, const QVariantMap &sharedMap) const;
virtual void storeSharedSettings(const QVariantMap &data) const;
virtual QVariant retrieveSharedSettings() const;
QVariantMap mergeSettings(const QVariantMap &userMap, const QVariantMap &sharedMap) const;
Utils::optional<Issue> findIssues(const QVariantMap &data, const Utils::FileName &path) const;
private:
Utils::FileNameList settingsFiles(const QString &suffix) const;
Utils::FileNameList settingsFiles() const;
QByteArray settingsId() const;
QString displayName() const;
QString defaultFileName(const QString &suffix) const;
void backupUserFile() const;
QVariantMap readUserSettings(QWidget *parent) const;
QVariantMap readSharedSettings(QWidget *parent) const;
RestoreData readUserSettings(QWidget *parent) const;
RestoreData readSharedSettings(QWidget *parent) const;
static QByteArray settingsIdFromMap(const QVariantMap &data);
static QString differentEnvironmentMsg(const QString &projectName);

View File

@@ -233,10 +233,10 @@ private slots:
void testToolChainManager_data();
void testToolChainManager();
void testUserFileAccessor_prepareSettings();
void testUserFileAccessor_prepareSettingsObsoleteVersion();
void testUserFileAccessor_prepareSettingsObsoleteVersionNewVersion();
void testUserFileAccessor_prepareToSaveSettings();
void testUserFileAccessor_prepareToReadSettings();
void testUserFileAccessor_prepareToReadSettingsObsoleteVersion();
void testUserFileAccessor_prepareToReadSettingsObsoleteVersionNewVersion();
void testUserFileAccessor_prepareToWriteSettings();
void testUserFileAccessor_mergeSettings();
void testUserFileAccessor_mergeSettingsEmptyUser();
void testUserFileAccessor_mergeSettingsEmptyShared();

View File

@@ -327,12 +327,10 @@ static QVariantMap processHandlerNodes(const HandlerNode &node, const QVariantMa
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project) :
SettingsAccessor(project->projectFilePath(), "QtCreatorProject"),
SettingsAccessor(project->projectFilePath(), "QtCreatorProject", project->displayName(), Core::Constants::IDE_DISPLAY_NAME),
m_project(project)
{
setSettingsId(ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray());
setDisplayName(project->displayName());
setApplicationDisplayName(Core::Constants::IDE_DISPLAY_NAME);
// Register Upgraders:
addVersionUpgrader(std::make_unique<UserFileVersion1Upgrader>(this));
@@ -359,12 +357,12 @@ Project *UserFileAccessor::project() const
return m_project;
}
QVariantMap UserFileAccessor::prepareSettings(const QVariantMap &data) const
QVariantMap UserFileAccessor::preprocessReadSettings(const QVariantMap &data) const
{
// Move from old Version field to new one:
// This can not be done in a normal upgrader since the version information is needed
// to decide which upgraders to run
QVariantMap result = SettingsAccessor::prepareSettings(data);
QVariantMap result = SettingsAccessor::preprocessReadSettings(data);
const QString obsoleteKey = OBSOLETE_VERSION_KEY;
if (data.contains(obsoleteKey)) {
result = setVersionInMap(result, data.value(obsoleteKey, versionFromMap(data)).toInt());
@@ -373,9 +371,9 @@ QVariantMap UserFileAccessor::prepareSettings(const QVariantMap &data) const
return result;
}
QVariantMap UserFileAccessor::prepareToSaveSettings(const QVariantMap &data) const
QVariantMap UserFileAccessor::prepareToWriteSettings(const QVariantMap &data) const
{
QVariantMap tmp = SettingsAccessor::prepareToSaveSettings(data);
QVariantMap tmp = SettingsAccessor::prepareToWriteSettings(data);
// for compatibility with QtC 3.1 and older:
tmp.insert(OBSOLETE_VERSION_KEY, currentVersion());
@@ -1984,8 +1982,8 @@ public:
void storeSharedSettings(const QVariantMap &data) const final { m_storedSettings = data; }
QVariant retrieveSharedSettings() const final { return m_storedSettings; }
using UserFileAccessor::prepareSettings;
using UserFileAccessor::prepareToSaveSettings;
using UserFileAccessor::preprocessReadSettings;
using UserFileAccessor::prepareToWriteSettings;
using UserFileAccessor::mergeSettings;
@@ -2004,7 +2002,7 @@ public:
} // namespace
void ProjectExplorerPlugin::testUserFileAccessor_prepareSettings()
void ProjectExplorerPlugin::testUserFileAccessor_prepareToReadSettings()
{
TestProject project;
TestUserFileAccessor accessor(&project);
@@ -2013,12 +2011,12 @@ void ProjectExplorerPlugin::testUserFileAccessor_prepareSettings()
data.insert("Version", 4);
data.insert("Foo", "bar");
QVariantMap result = accessor.prepareSettings(data);
QVariantMap result = accessor.preprocessReadSettings(data);
QCOMPARE(result, data);
}
void ProjectExplorerPlugin::testUserFileAccessor_prepareSettingsObsoleteVersion()
void ProjectExplorerPlugin::testUserFileAccessor_prepareToReadSettingsObsoleteVersion()
{
TestProject project;
TestUserFileAccessor accessor(&project);
@@ -2027,14 +2025,14 @@ void ProjectExplorerPlugin::testUserFileAccessor_prepareSettingsObsoleteVersion(
data.insert("ProjectExplorer.Project.Updater.FileVersion", 4);
data.insert("Foo", "bar");
QVariantMap result = accessor.prepareSettings(data);
QVariantMap result = accessor.preprocessReadSettings(data);
QCOMPARE(result.count(), data.count());
QCOMPARE(result.value("Foo"), data.value("Foo"));
QCOMPARE(result.value("Version"), data.value("ProjectExplorer.Project.Updater.FileVersion"));
}
void ProjectExplorerPlugin::testUserFileAccessor_prepareSettingsObsoleteVersionNewVersion()
void ProjectExplorerPlugin::testUserFileAccessor_prepareToReadSettingsObsoleteVersionNewVersion()
{
TestProject project;
TestUserFileAccessor accessor(&project);
@@ -2044,7 +2042,7 @@ void ProjectExplorerPlugin::testUserFileAccessor_prepareSettingsObsoleteVersionN
data.insert("Version", 5);
data.insert("Foo", "bar");
QVariantMap result = accessor.prepareSettings(data);
QVariantMap result = accessor.preprocessReadSettings(data);
QCOMPARE(result.count(), data.count() - 1);
QCOMPARE(result.value("Foo"), data.value("Foo"));
@@ -2052,7 +2050,7 @@ void ProjectExplorerPlugin::testUserFileAccessor_prepareSettingsObsoleteVersionN
QCOMPARE(result.value("Version"), data.value("ProjectExplorer.Project.Updater.FileVersion"));
}
void ProjectExplorerPlugin::testUserFileAccessor_prepareToSaveSettings()
void ProjectExplorerPlugin::testUserFileAccessor_prepareToWriteSettings()
{
TestProject project;
TestUserFileAccessor accessor(&project);
@@ -2070,7 +2068,7 @@ void ProjectExplorerPlugin::testUserFileAccessor_prepareToSaveSettings()
data.insert("shared1", "bar1");
data.insert("unique1", 1234);
data.insert("shared3", "foo");
QVariantMap result = accessor.prepareToSaveSettings(data);
QVariantMap result = accessor.prepareToWriteSettings(data);
QCOMPARE(result.count(), data.count() + 3);
QCOMPARE(result.value("EnvironmentId").toByteArray(),

View File

@@ -45,8 +45,8 @@ public:
Project *project() const;
protected:
QVariantMap prepareSettings(const QVariantMap &data) const final;
QVariantMap prepareToSaveSettings(const QVariantMap &data) const final;
QVariantMap preprocessReadSettings(const QVariantMap &data) const final;
QVariantMap prepareToWriteSettings(const QVariantMap &data) const final;
void storeSharedSettings(const QVariantMap &data) const override;
QVariant retrieveSharedSettings() const override;

View File

@@ -72,10 +72,8 @@ class BasicTestSettingsAccessor : public Utils::SettingsAccessor
public:
BasicTestSettingsAccessor(const Utils::FileName &baseName = Utils::FileName::fromString("/foo/bar"),
const QByteArray &id = QByteArray(TESTACCESSOR_DEFAULT_ID)) :
Utils::SettingsAccessor(baseName, "TestData")
Utils::SettingsAccessor(baseName, "TestData", TESTACCESSOR_DN, TESTACCESSOR_APPLICATION_DN)
{
setDisplayName(TESTACCESSOR_DN);
setApplicationDisplayName(TESTACCESSOR_APPLICATION_DN);
setSettingsId(id);
}
};
@@ -486,7 +484,7 @@ void tst_SettingsAccessor::findIssues_ok()
const QVariantMap data = versionedMap(6, TESTACCESSOR_DEFAULT_ID);
const Utils::FileName path = Utils::FileName::fromString("/foo/baz.user");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(!info);
}
@@ -497,7 +495,7 @@ void tst_SettingsAccessor::findIssues_emptyData()
const QVariantMap data;
const Utils::FileName path = Utils::FileName::fromString("/foo/bar.user");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(bool(info));
}
@@ -508,7 +506,7 @@ void tst_SettingsAccessor::findIssues_tooNew()
const QVariantMap data = versionedMap(42, TESTACCESSOR_DEFAULT_ID);
const Utils::FileName path = Utils::FileName::fromString("/foo/bar.user");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(bool(info));
}
@@ -519,7 +517,7 @@ void tst_SettingsAccessor::findIssues_tooOld()
const QVariantMap data = versionedMap(2, TESTACCESSOR_DEFAULT_ID);
const Utils::FileName path = Utils::FileName::fromString("/foo/bar.user");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(bool(info));
}
@@ -530,7 +528,7 @@ void tst_SettingsAccessor::findIssues_wrongId()
const QVariantMap data = versionedMap(6, "foo");
const Utils::FileName path = Utils::FileName::fromString("/foo/bar.user");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(bool(info));
}
@@ -541,7 +539,7 @@ void tst_SettingsAccessor::findIssues_nonDefaultPath()
const QVariantMap data = versionedMap(6, TESTACCESSOR_DEFAULT_ID);
const Utils::FileName path = Utils::FileName::fromString("/foo/bar.user.foobar");
const Utils::optional<Utils::SettingsAccessor::IssueInfo> info = accessor.findIssues(data, path);
const Utils::optional<Utils::SettingsAccessor::Issue> info = accessor.findIssues(data, path);
QVERIFY(bool(info));
}