diff --git a/include/wx/fileconf.h b/include/wx/fileconf.h index 87f698a38f..4229820cf8 100644 --- a/include/wx/fileconf.h +++ b/include/wx/fileconf.h @@ -123,6 +123,22 @@ public: return GetLocalFile(szFile, style).GetFullPath(); } + // Function to migrate, i.e. move, an existing local config file to another + // location. Old and new style determine the existing and new file paths. + struct MigrationResult + { + // If empty, it means the old file wasn't found and nothing was done. + wxString oldPath; + + // The name of the new file. + wxString newPath; + + // If empty, means the file was successfully migrated. + wxString error; + }; + static MigrationResult + MigrateLocalFile(const wxString& name, int newStyle, int oldStyle = 0); + // ctor & dtor // New constructor: one size fits all. Specify wxCONFIG_USE_LOCAL_FILE or // wxCONFIG_USE_GLOBAL_FILE to say which files should be used. diff --git a/interface/wx/fileconf.h b/interface/wx/fileconf.h index ae91b8e481..05f3c14b21 100644 --- a/interface/wx/fileconf.h +++ b/interface/wx/fileconf.h @@ -25,7 +25,8 @@ path is backwards-compatible but not recommended any more and it is advised to call wxStandardPaths::SetFileLayout() with wxStandardPaths::FileLayout_XDG parameter to change the default path to - `~/.config/appname.conf`. + `~/.config/appname.conf`. MigrateLocalFile() may be helpful for moving the + existing configuration file to the new location. Alternatively, it is possible to specify ::wxCONFIG_USE_XDG flag in the style parameter of the constructor to use this XDG-compliant path without @@ -99,6 +100,70 @@ public: static wxString GetGlobalFileName(const wxString& szFile); static wxString GetLocalFileName(const wxString& szFile, int style = 0); + /** + Contains return value of MigrateLocalFile(). + + @since 3.3.0 + */ + struct MigrationResult + { + /// If empty, it means the old file wasn't found and nothing was done. + wxString oldPath; + + /// The name of the new file. + wxString newPath; + + /// If empty, means the file was successfully migrated. + wxString error; + }; + + /** + Move the existing configuration file to a new location. + + This function is useful for moving legacy configuration files in + `~/.appname` or `~/.appname/appname.conf` to the XDG-compliant location + under `~/.config`. To do this, simply specify ::wxCONFIG_USE_XDG as + part of @a newStyle. + + The returned MigrationResult object describes what, if anything, was + done: if its `oldPath` member is empty, it means that the file + corresponding to @a oldStyle was not found and nothing was done. + Otherwise, if its `error` member is empty, the old file was found and + moved to `newPath`. And if `error` is not empty, it contains the + user-readable error message describing why moving the file failed. + + Typical example of using this function is shown in the widgets sample: + @code + // Execute this early during the application startup, before the + // global wxConfig object is created. + const auto res = wxFileConfig::MigrateLocalFile("app", wxCONFIG_USE_XDG); + if ( !res.oldPath.empty() ) { + if ( res.error.empty() ) { + wxLogMessage("Config file was migrated from \"%s\" to \"%s\"", + res.oldPath, res.newPath); + } else { + wxLogWarning("Migrating old config failed: %s.", res.error); + } + } + + // Note that this must be done after calling MigrateLocalFile(), + // otherwise the old style would use XDG layout already and the actual + // file at non-XDG-compliant location wouldn't be migrated. + wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); + @endcode + + @param name Name of the configuration file. + @param newStyle Style which is used by the current version of the + program, typically including ::wxCONFIG_USE_XDG and possibly also + including ::wxCONFIG_USE_SUBDIR. + @param oldStyle Style which was used by the previous versions of the + program, possibly including ::wxCONFIG_USE_SUBDIR. + + @since 3.3.0 + */ + static MigrationResult + MigrateLocalFile(const wxString& name, int newStyle, int oldStyle = 0); + /** Saves all config data to the given stream, returns @true if data was saved successfully or @false on error. diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index 263e8ddac5..9add0d857d 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -37,6 +37,9 @@ #include "wx/msgdlg.h" #endif +#include "wx/config.h" +#include "wx/stdpaths.h" + #include "wx/sysopt.h" #include "wx/bookctrl.h" #include "wx/treebook.h" @@ -141,6 +144,30 @@ public: #if USE_LOG m_logTarget = nullptr; #endif // USE_LOG + +#ifdef wxHAS_CONFIG_AS_FILECONFIG + // We want to put our config file (implicitly created for persistent + // controls settings) in XDG-compliant location, so we want to change + // the default file layout, but before doing this migrate any existing + // config files to the new location as the previous versions of this + // sample didn't use XDG layout. + const auto + res = wxFileConfig::MigrateLocalFile("widgets", wxCONFIG_USE_XDG); + if ( !res.oldPath.empty() ) + { + if ( res.error.empty() ) + { + wxLogMessage("Config file was migrated from \"%s\" to \"%s\"", + res.oldPath, res.newPath); + } + else + { + wxLogWarning("Migrating old config failed: %s.", res.error); + } + } + + wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG); +#endif // wxHAS_CONFIG_AS_FILECONFIG } WidgetsApp(const WidgetsApp&) = delete; WidgetsApp& operator=(const WidgetsApp&) = delete; diff --git a/src/common/fileconf.cpp b/src/common/fileconf.cpp index 494913275b..17e6b6469e 100644 --- a/src/common/fileconf.cpp +++ b/src/common/fileconf.cpp @@ -296,6 +296,105 @@ wxFileName wxFileConfig::GetLocalFile(const wxString& szFile, int style) return wxFileName(GetLocalDir(style), stdp.MakeConfigFileName(szFile, conv)); } +wxFileConfig::MigrationResult +wxFileConfig::MigrateLocalFile(const wxString& name, int newStyle, int oldStyle) +{ + MigrationResult res; + + const auto oldPath = GetLocalFile(name, oldStyle); + if ( !oldPath.FileExists() ) + return res; + + const auto newPath = GetLocalFile(name, newStyle); + if ( newPath == oldPath ) + return res; + + res.oldPath = oldPath.GetFullPath(); + res.newPath = newPath.GetFullPath(); + + // This class ensures that we (at least try to) rename the existing config + // file back to its original name if we fail with an error. + class RenameBackOnError + { + public: + explicit RenameBackOnError(wxFileConfig::MigrationResult& res) + : m_res(res) + { + } + + void Init(const wxString& tempPath) + { + m_tempPath = tempPath; + } + + void Dismiss() + { + m_tempPath.clear(); + } + + ~RenameBackOnError() + { + if ( !m_tempPath.empty() ) + { + if ( !wxRenameFile(m_tempPath, m_res.oldPath) ) + { + // This should never happen, but if it does, do at least + // let the user know that we moved their file to a wrong + // place and couldn't put it back. + m_res.error += wxString::Format( + _(" and additionally, the existing configuration file" + " was renamed to \"%s\" and couldn't be renamed back," + " please rename it to its original path \"%s\""), + m_tempPath, + m_res.oldPath + ); + } + } + } + + private: + MigrationResult& m_res; + + wxString m_tempPath; + } renameBackOnError{res}; + + wxString currentPath = res.oldPath; + + const auto newDir = newPath.GetPath(); + if ( !wxFileName::DirExists(newDir) ) + { + // There is an annoying failure mode here when the new directory can't + // be created because its name is the same as the name of the existing + // file, e.g. when oldStyle==0 and newStyle==wxCONFIG_USE_SUBDIR and + // XDG layout is not used, so check for this specially. + if ( newDir == res.oldPath ) + { + currentPath = wxFileName::CreateTempFileName(currentPath); + if ( !wxRenameFile(res.oldPath, currentPath) ) + { + res.error = _("failed to rename the existing file"); + return res; + } + + renameBackOnError.Init(currentPath); + } + + if ( !wxFileName::Mkdir(newDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL) ) + { + res.error = _("failed to create the new file directory"); + return res; + } + } + + if ( !wxRenameFile(currentPath, res.newPath) ) + { + res.error = _("failed to move the file to the new location"); + return res; + } + + return res; +} + // ---------------------------------------------------------------------------- // ctor // ----------------------------------------------------------------------------