diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index a0330789c9cba38538f62a37e40d8199b6c8f3f1..8b1df8fbc23cd983aa1b2bb659aadb85832e1b99 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -134,9 +134,22 @@ signals: void aboutToSaveSettings(); protected: - // restore all data from the map. + // The methods toMap/fromMap are used for serialization of settings data. By default, all + // settings are saved in the .user file. For shared settings, one needs to explicitly + // specify which keys from a particular map should be saved in the .shared file. This is + // done in the following way: // - // Note: Do not forget to call your base class' fromMap method! + // - Create an item in the map with the key SHARED_SETTINGS_KEYS_KEY and a QStringList + // as a value. If everything from this particular map should be shared simply add + // the unique item ALL_SETTINGS_KEYS_KEY to the QStringList. Otherwise, add to the + // QStringList the keys that should be shared. + // + // Notice that the sharing process is smart enough in terms of recursion and grouping of + // keys. This means that shared keys from deeply nested maps don't need to be propagated + // anyhow to the top-level map. Simply add them from within the map they actually belong. + // + // The other thing to notice is that shared keys are not really excluded from the user + // settings file. More details about that in the SettingsAcessor. virtual bool fromMap(const QVariantMap &map); virtual void setProjectContext(Core::Context context); diff --git a/src/plugins/projectexplorer/projectexplorerconstants.h b/src/plugins/projectexplorer/projectexplorerconstants.h index a6c71948922171045d56f55cec5374db2af25a7f..002f7e49133034a5927c4c4c51e031e1f156e789 100644 --- a/src/plugins/projectexplorer/projectexplorerconstants.h +++ b/src/plugins/projectexplorer/projectexplorerconstants.h @@ -217,6 +217,10 @@ const int QML_DEFAULT_DEBUG_SERVER_PORT = 3768; // Default directory to run custom (build) commands in. const char DEFAULT_WORKING_DIR[] = "%{buildDir}"; +// Settings files keys +const char SHARED_SETTINGS_KEYS_KEY[] = "ProjectExplorer.SharedSettingsKeysKey"; +const char ALL_SETTINGS_KEYS_KEY[] = "ProjectExplorer.AllSettingsKeysKey"; + } // namespace Constants } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/settingsaccessor.cpp b/src/plugins/projectexplorer/settingsaccessor.cpp index f7653d745292c7bc6ec8de59faba353b8891aa61..edacf0c18041620188023b5dda8456ecfc6b5c77 100644 --- a/src/plugins/projectexplorer/settingsaccessor.cpp +++ b/src/plugins/projectexplorer/settingsaccessor.cpp @@ -120,6 +120,8 @@ namespace { const char VERSION_KEY[] = "ProjectExplorer.Project.Updater.FileVersion"; const char ENVIRONMENT_ID_KEY[] = "ProjectExplorer.Project.Updater.EnvironmentId"; +const char USE_SHARED_SETTINGS[] = "UseSharedSettings"; + // Version 0 is used in Qt Creator 1.3.x and // (in a slighly different flavour) post 1.3 master. class Version0Handler : public UserFileVersionHandler @@ -390,7 +392,13 @@ SettingsAccessor::SettingsAccessor() : m_userFileAcessor(QByteArray("qtcUserFileName"), QLatin1String(".user"), QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), - true) + true, + true), + m_sharedFileAcessor(QByteArray("qtcSharedFileName"), + QLatin1String(".shared"), + QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), + false, + false) { addVersionHandler(new Version0Handler); addVersionHandler(new Version1Handler); @@ -415,16 +423,74 @@ SettingsAccessor *SettingsAccessor::instance() return &acessor; } +namespace { + +// For the functions below, the full map must prepopulated with the user settings so it +// has the corresponding keys for the ones in the shared settings (remember that the +// shared settings are backedup in the user settings). + +void mergeSharedSettings(QVariantMap *fullMap, const QVariantMap &sharedMap) +{ + QVariantMap::const_iterator it = sharedMap.begin(); + QVariantMap::const_iterator eit = sharedMap.end(); + for (; it != eit; ++it) { + const QString &key = it.key(); + QVariant value = it.value(); + if (value.type() == QVariant::Map) { + const QVariant &correspondingValue = fullMap->value(key); + if (correspondingValue.type() != QVariant::Map) { + // This should happen only if the user manually changed the file in such a way. + continue; + } + QVariantMap nestedMap = correspondingValue.toMap(); + mergeSharedSettings(&nestedMap, value.toMap()); + value = nestedMap; + } + if (fullMap->contains(key)) + fullMap->insert(key, value); // Replace with the shared setting. + } +} + +void splitSharedSettings(QVariantMap *sharedMap, const QVariantMap &fullMap) +{ + const QStringList &controlKey = + fullMap.value(QLatin1String(Constants::SHARED_SETTINGS_KEYS_KEY)).toStringList(); + if (controlKey.size() == 1 + && controlKey.at(0) == QLatin1String(Constants::ALL_SETTINGS_KEYS_KEY)) { + *sharedMap = fullMap; + } else { + const QSet<QString> &shared = QSet<QString>::fromList(controlKey); + QVariantMap::iterator it = fullMap.begin(); + QVariantMap::iterator eit = fullMap.end(); + for (; it != eit; ++it) { + const QString &key = it.key(); + const QVariant &value = it.value(); + if (shared.contains(key)) { + sharedMap->insert(key, value); + } else { + if (value.type() == QVariant::Map) { + QVariantMap nestedMap; + splitSharedSettings(&nestedMap, value.toMap()); + if (!nestedMap.isEmpty()) + sharedMap->insert(key, nestedMap); + } + } + } + } +} + +} // Anonymous + QVariantMap SettingsAccessor::restoreSettings(Project *project) const { if (m_lastVersion < 0 || !project) return QVariantMap(); - SettingsData settings; - if (!m_userFileAcessor.readFile(project, &settings)) + SettingsData userSettings; + if (!m_userFileAcessor.readFile(project, &userSettings)) return QVariantMap(); - if (settings.m_version > SettingsAccessor::instance()->m_lastVersion + 1) { + if (userSettings.m_version > SettingsAccessor::instance()->m_lastVersion + 1) { QMessageBox::information( Core::ICore::instance()->mainWindow(), QApplication::translate("ProjectExplorer::SettingsAccessor", @@ -442,7 +508,7 @@ QVariantMap SettingsAccessor::restoreSettings(Project *project) const } // Verify environment. - if (!verifyEnvironmentId(settings.m_map.value(QLatin1String(ENVIRONMENT_ID_KEY)).toString())) { + if (!verifyEnvironmentId(userSettings.m_map.value(QLatin1String(ENVIRONMENT_ID_KEY)).toString())) { // TODO tr, casing check QMessageBox msgBox( QMessageBox::Question, @@ -465,19 +531,72 @@ QVariantMap SettingsAccessor::restoreSettings(Project *project) const } // Do we need to generate a backup? - if (settings.m_version < m_lastVersion + 1 && !settings.m_usingBackup) { - const QString &backupFileName = settings.m_fileName + if (userSettings.m_version < m_lastVersion + 1 && !userSettings.m_usingBackup) { + const QString &backupFileName = userSettings.m_fileName + '.' - + m_handlers.value(settings.m_version)->displayUserFileVersion(); + + m_handlers.value(userSettings.m_version)->displayUserFileVersion(); QFile::remove(backupFileName); // Remove because copy doesn't overwrite - QFile::copy(settings.m_fileName, backupFileName); + QFile::copy(userSettings.m_fileName, backupFileName); + } + + + // Time to restore the shared settings. + bool useSharedSettings = true; + int commonFileVersion = userSettings.m_version; + SettingsData sharedSettings; + if (m_sharedFileAcessor.readFile(project, &sharedSettings)) { + if (sharedSettings.m_version != userSettings.m_version) { + // At this point the user file version can only be <= Creator's version. This is not + // true for the shared file version. In the case the shared version is newer than + // Creator's we prompt the user whether we could try an *unsupported* update. This + // makes sense for the shared file since the merging operation will only replace the + // settings that actually match the corresponding ones in the user file. + if (sharedSettings.m_version > m_lastVersion + 1) { + QMessageBox msgBox( + QMessageBox::Question, + QApplication::translate("ProjectExplorer::SettingsAccessor", + "Unsupported Shared Settings File"), + QApplication::translate("ProjectExplorer::SettingsAccessor", + "The version of your .shared file is not yet " + "supported. Qt Creator can still load it and " + "look for compatible settings (the file will " + "be overwritten).\n\n" + "Do you want to continue?\n\n" + "If you choose not to continue Qt Creator will " + "use the corresponding settings which are " + "backed-up in your .user file.\n\n"), + QMessageBox::Yes | QMessageBox::No, + Core::ICore::instance()->mainWindow()); + msgBox.setDefaultButton(QMessageBox::No); + msgBox.setEscapeButton(QMessageBox::No); + if (msgBox.exec() == QMessageBox::No) + useSharedSettings = false; + else + commonFileVersion = m_lastVersion + 1; + } else { + commonFileVersion = qMax(userSettings.m_version, sharedSettings.m_version); + } + + if (useSharedSettings) { + // We now update the user and shared settings so they are compatible. + for (int i = userSettings.m_version; i < commonFileVersion; ++i) + userSettings.m_map = m_handlers.value(i)->update(project, userSettings.m_map); + for (int i = sharedSettings.m_version; i < commonFileVersion; ++i) + sharedSettings.m_map = m_handlers.value(i)->update(project, sharedSettings.m_map); + } + } } - // Update version. - for (int i = settings.m_version; i <= m_lastVersion; ++i) - settings.m_map = m_handlers.value(i)->update(project, settings.m_map); + QVariantMap mergeMap = userSettings.m_map; + if (useSharedSettings && !sharedSettings.m_map.isEmpty()) + mergeSharedSettings(&mergeMap, sharedSettings.m_map); + project->setProperty(USE_SHARED_SETTINGS, useSharedSettings); + + // Finally update from the common version to Creator's version. + for (int i = commonFileVersion; i <= m_lastVersion; ++i) + mergeMap = m_handlers.value(i)->update(project, mergeMap); - return settings.m_map; + return mergeMap; } bool SettingsAccessor::saveSettings(const Project *project, const QVariantMap &map) const @@ -486,7 +605,16 @@ bool SettingsAccessor::saveSettings(const Project *project, const QVariantMap &m return false; SettingsData settings(map); - return m_userFileAcessor.writeFile(project, &settings); + if (m_userFileAcessor.writeFile(project, &settings)) { + if (!project->property(USE_SHARED_SETTINGS).toBool()) + return true; + + SettingsData sharedSettings; + splitSharedSettings(&sharedSettings.m_map, map); + return m_sharedFileAcessor.writeFile(project, &sharedSettings); + } + + return false; } void SettingsAccessor::addVersionHandler(UserFileVersionHandler *handler) @@ -536,9 +664,11 @@ bool SettingsAccessor::verifyEnvironmentId(const QString &id) SettingsAccessor::FileAccessor::FileAccessor(const QByteArray &id, const QString &defaultSuffix, const QString &environmentSuffix, - bool envSpecific) + bool envSpecific, + bool versionStrict) : m_id(id) , m_environmentSpecific(envSpecific) + , m_versionStrict(versionStrict) { assignSuffix(defaultSuffix, environmentSuffix); } @@ -636,6 +766,9 @@ bool SettingsAccessor::FileAccessor::readFile(Project *project, // Get and verify file version settings->m_version = settings->m_map.value(QLatin1String(VERSION_KEY), 0).toInt(); + if (!m_versionStrict) + return true; + if (settings->m_version < SettingsAccessor::instance()->m_firstVersion) { qWarning() << "Version" << settings->m_version << "in" << m_suffix << "too old."; return false; diff --git a/src/plugins/projectexplorer/settingsaccessor.h b/src/plugins/projectexplorer/settingsaccessor.h index b59665da760f1b578112216322136f3db2ccb7aa..55e4663b4f7258d589b540b97e7fd799c01e0fe3 100644 --- a/src/plugins/projectexplorer/settingsaccessor.h +++ b/src/plugins/projectexplorer/settingsaccessor.h @@ -44,6 +44,35 @@ namespace Internal { class UserFileVersionHandler; } +/* + The SettingsAcessor + + The SettingsAcessor currently persists a user-specific file and a shared file. By default + settings are user-specific and the shared ones are identified through the key + SHARED_SETTINGS_KEYS_KEY in the map. The corresponding value for this key must be a + QStringList containing the keys from this particular map that should be shared. As an aid, + if the QStringList contains a unique item ALL_SETTINGS_KEYS_KEY all keys from the map + are considered shared. + + While we are very strict regarding versions from the user settings (we create backups for + old versions, always try to find the correct file, etc), this is not the same for the shared + files. When considering many usability aspects and issues like having them in repositories + (and unintenional overwrites when pulling), simply passing them around, matching their + version against the user version, it seems that the more simplistic approach would be the + best (at least for now). + + The idea is that shared settings are never removed from the user settings file, they work + as a backup when the different "synchronization" issues arise. We then make sure that + whenever settings are restored we start with the user ones and eventually merge the shared + ones into the final map, replacing the corresponding ones in the user map (so the shared + ones take precedences). An analoguous behavior happens when saving the settings. + + The main benefit of this approach is that we always try to use the shared settings. When + possible we run the version update handlers and make the shared settings compatible with + the user settings (or vice-versa). From that on it's transparent for handlers to update + the settings to the current Creator version (if it's the case) since it's just one map. + + */ class SettingsAccessor { public: @@ -80,7 +109,8 @@ private: FileAccessor(const QByteArray &id, const QString &defaultSuffix, const QString &environmentSuffix, - bool envSpecific); + bool envSpecific, + bool versionStrict); bool readFile(Project *project, SettingsData *settings) const; bool writeFile(const Project *project, const SettingsData *settings) const; @@ -93,6 +123,7 @@ private: QByteArray m_id; QString m_suffix; bool m_environmentSpecific; + bool m_versionStrict; }; static bool verifyEnvironmentId(const QString &id); @@ -101,6 +132,7 @@ private: int m_firstVersion; int m_lastVersion; const FileAccessor m_userFileAcessor; + const FileAccessor m_sharedFileAcessor; }; } // namespace ProjectExplorer