Add wxFileConfig::MigrateLocalFile()

This can be useful for the existing applications switching to using
XDG-compliant config files location as they can just call this function
on startup to keep using the existing file.
This commit is contained in:
Vadim Zeitlin 2024-01-04 03:05:44 +01:00
parent ebe0847932
commit 486865b446
4 changed files with 208 additions and 1 deletions

View file

@ -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.

View file

@ -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.

View file

@ -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;

View file

@ -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
// ----------------------------------------------------------------------------