diff --git a/src/Main/CMakeLists.txt b/src/Main/CMakeLists.txt index 214d62f5b..1736fa962 100644 --- a/src/Main/CMakeLists.txt +++ b/src/Main/CMakeLists.txt @@ -187,4 +187,5 @@ endif() if (COMMAND flightgear_test) flightgear_test(posinit test_posinit.cxx) + flightgear_test(autosaveMigration test_autosaveMigration.cxx) endif() diff --git a/src/Main/globals.cxx b/src/Main/globals.cxx index 946739dbc..8f7102333 100644 --- a/src/Main/globals.cxx +++ b/src/Main/globals.cxx @@ -675,6 +675,103 @@ SGPath FGGlobals::autosaveFilePath(SGPath userDataPath) const return simgear::Dir(userDataPath).file(autosaveName()); } +static void deleteProperties(SGPropertyNode* props, const string_list& blacklist) +{ + const std::string path(props->getPath()); + auto it = std::find_if(blacklist.begin(), blacklist.end(), [path](const std::string& black) + { return simgear::strutils::matchPropPathToTemplate(path, black); }); + if (it != blacklist.end()) { + SGPropertyNode* pr = props->getParent(); + pr->removeChild(props); + return; + } + + // recurse + for (int c=0; c < props->nChildren(); ++c) { + deleteProperties(props->getChild(c), blacklist); + } + +} + +using VersionPair = std::pair; + +static bool operator<(const VersionPair& a, const VersionPair& b) +{ + if (a.first == b.first) { + return a.second < b.second; + } + + return a.first < b.first; +} + +static void tryAutosaveMigration(const SGPath& userDataPath, SGPropertyNode* props) +{ + const string_list versionParts = simgear::strutils::split(VERSION, "."); + if (versionParts.size() < 2) { + return; + } + + simgear::Dir userDataDir(userDataPath); + SGPath migratePath; + VersionPair foundVersion(0, 0); + const VersionPair currentVersion(simgear::strutils::to_int(versionParts[0]), + simgear::strutils::to_int(versionParts[1])); + + for (auto previousSave : userDataDir.children(simgear::Dir::TYPE_FILE, ".xml")) { + const std::string base = previousSave.file_base(); + VersionPair v; + // extract version from name + const int matches = ::sscanf(base.c_str(), "autosave_%d_%d", &v.first, &v.second); + if (matches != 2) { + continue; + } + + if (currentVersion < v) { + // ignore autosaves from more recent versions; this happens when + // running unsable and stable at the same time + continue; + } + + if (v.first < 2000) { + // ignore autosaves from older versions, too much change to deal + // with. + continue; + } + + if (foundVersion < v) { + foundVersion = v; + migratePath = previousSave; + } + } + + if (!migratePath.exists()) { + return; + } + + SG_LOG(SG_GENERAL, SG_INFO, "Migrating old autosave:" << migratePath); + SGPropertyNode oldProps; + + try { + readProperties(migratePath, &oldProps, SGPropertyNode::USERARCHIVE); + } catch (sg_exception& e) { + SG_LOG(SG_GENERAL, SG_WARN, "failed to read previous user settings:" << e.getMessage() + << "(from " << e.getOrigin() << ")"); + return; + } + + // read migration blacklist + string_list blacklist; + for (auto node : fgGetNode("/sim/autosave-migration/blacklist")->getChildren("path")) { + blacklist.push_back(node->getStringValue()); + } + + // apply migration filters for each property in turn + deleteProperties(&oldProps, blacklist); + + // copy remaining props out + copyProperties(&oldProps, props); +} + // Load user settings from the autosave file (normally in $FG_HOME) void FGGlobals::loadUserSettings(SGPath userDataPath) @@ -697,6 +794,10 @@ FGGlobals::loadUserSettings(SGPath userDataPath) SG_LOG(SG_INPUT, SG_WARN, "failed to read user settings:" << e.getMessage() << "(from " << e.getOrigin() << ")"); } + } else { +#if 0 + tryAutosaveMigration(userDataPath, &autosave); +#endif } copyProperties(&autosave, globals->get_props()); } diff --git a/src/Main/test_autosaveMigration.cxx b/src/Main/test_autosaveMigration.cxx new file mode 100644 index 000000000..2e13b6f49 --- /dev/null +++ b/src/Main/test_autosaveMigration.cxx @@ -0,0 +1,166 @@ +// Written by James Turner, started 2017. +// +// Copyright (C) 2017 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#include "config.h" + +#include "unitTestHelpers.hxx" + +#include +#include +#include +#include + +#include "Main/globals.hxx" +#include "Main/options.hxx" +#include "Main/fg_props.hxx" + +using namespace flightgear; + +void writeLegacyAutosave(SGPath userData, int majorVersion, int minorVersion) +{ + std::ostringstream os; + os << "autosave_" << majorVersion << "_" << minorVersion << ".xml"; + + sg_ofstream of(userData / os.str()); + { + of << "" \ + "" \ + "" \ + "42" \ + "" \ + "12" \ + "12" \ + "" \ + "" \ + "13" \ + "" \ + "" \ + "10" \ + "512" \ + "" \ + "abc" \ + "abc" \ + "" \ + "" \ + "" \ + "" \ + "button" \ + "" \ + "" \ + "slider" \ + ""\ + "" \ + "" \ + "888" \ + "" \ + "" \ + "somevalue" \ + "somevalue" \ + "" \ + "" \ + ""; + + } + of.close(); +} + +void writeLegacyAutosave2(SGPath userData, int majorVersion, int minorVersion) +{ + std::ostringstream os; + os << "autosave_" << majorVersion << "_" << minorVersion << ".xml"; + + sg_ofstream of(userData / os.str()); + { + of << "" \ + "" \ + "" \ + "1" \ + "" \ + "" \ + ""; + + } + of.close(); +} + + +void testMigration() +{ + fgtest::initTestGlobals("autosaveMigration"); + + Options::reset(); + + SGPath testUserDataPath = globals->get_fg_home() / "test_autosave_migrate"; + if (!testUserDataPath.exists()) { + SGPath p = testUserDataPath / "foo"; + p.create_dir(0755); + } + + simgear::Dir homeDir(testUserDataPath); + for (auto path : homeDir.children(simgear::Dir::TYPE_FILE, ".xml")) { + path.remove(); + } + + writeLegacyAutosave(testUserDataPath, 2016, 1); + + const string_list versionParts = simgear::strutils::split(VERSION, "."); + SG_VERIFY(versionParts.size() == 3); + const int currentMajor = simgear::strutils::to_int(versionParts[0]); + const int currentMinor = simgear::strutils::to_int(versionParts[1]); + + // none of these should not be read + writeLegacyAutosave2(testUserDataPath, 2016, 0); + writeLegacyAutosave2(testUserDataPath, currentMajor, currentMinor + 1); + writeLegacyAutosave2(testUserDataPath, currentMajor+1, currentMinor + 1); + + SGPath p = globals->autosaveFilePath(testUserDataPath); + if (p.exists()) { + SG_VERIFY(p.remove()); + } + + // write some blck-list rules to property tree + SGPropertyNode_ptr blacklist = fgGetNode("/sim/autosave-migration/blacklist", true); + blacklist->addChild("path")->setStringValue("/sim[0]/presets[0]/*"); + blacklist->addChild("path")->setStringValue("/sim[0]/rendering[0]/texture-"); + blacklist->addChild("path")->setStringValue("/views[0]/view[*]/old-prop"); + blacklist->addChild("path")->setStringValue("/sim[0]/gui"); + + // execute method under test + globals->loadUserSettings(testUserDataPath); + + SG_CHECK_EQUAL(globals->get_props()->getNode("sim")->getChildren("presets").size(), 2); + SG_CHECK_EQUAL(globals->get_props()->getNode("sim")->getChildren("gui").size(), 0); + + SG_CHECK_EQUAL(globals->get_props()->getIntValue("sim/window-height"), 42); + SG_CHECK_EQUAL(globals->get_props()->getIntValue("sim/presets/foo"), 0); + SG_CHECK_EQUAL(globals->get_props()->getIntValue("sim/presets[1]/foo"), 13); + + SG_CHECK_EQUAL(globals->get_props()->getIntValue("some-setting"), 888); + + // if this is not zero, one of the bad autosaves was read + SG_CHECK_EQUAL(globals->get_props()->getIntValue("sim/bad"), 0); + + + fgtest::shutdownTestGlobals(); +} + +int main(int argc, char* argv[]) +{ + testMigration(); + return EXIT_SUCCESS; +} diff --git a/src/Main/test_posinit.cxx b/src/Main/test_posinit.cxx index f746ab570..47883dc45 100644 --- a/src/Main/test_posinit.cxx +++ b/src/Main/test_posinit.cxx @@ -1,3 +1,21 @@ +// Written by James Turner, started 2017. +// +// Copyright (C) 2017 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + #include "config.h" #include "unitTestHelpers.hxx" @@ -114,6 +132,6 @@ int main(int argc, char* argv[]) testDefaultStartup(); testAirportOnlyStartup(); testAirportAndMetarStartup(); - + return EXIT_SUCCESS; }