Store a load order backup when applying a sort

Closes #735.
This commit is contained in:
Oliver Hamlet
2016-12-24 19:54:33 +00:00
parent 14a9cb355d
commit 617c13c4ee
6 changed files with 189 additions and 51 deletions
+5
View File
@@ -27,6 +27,11 @@ LOOT is able to sort plugins ghosted by Wrye Bash, and can extract Bash Tags and
Any errors encountered during sorting or masterlist update will be displayed on the "General Information" card.
Load Order Backups
^^^^^^^^^^^^^^^^^^
Before a sorted load order is applied, LOOT saves a backup of the current load order as a ``loadorder.bak.0`` text file in LOOT's data folder for the current game. Up to three load order backups are retained: ``loadorder.bak.0`` is the backup from the most recent load order change, ``loadorder.bak.1`` is the second-most recent backup, and ``loadorder.bak.2`` is the third-most recent backup.
Search
------
+1
View File
@@ -236,6 +236,7 @@ std::vector<std::string> Game::GetLoadOrder() const {
}
void Game::SetLoadOrder(const std::vector<std::string>& loadOrder) const {
BackupLoadOrder(loadOrder_, LootPaths::getLootDataPath() / FolderName());
LoadOrderHandler::SetLoadOrder(loadOrder);
loadOrder_ = loadOrder;
}
+19
View File
@@ -166,4 +166,23 @@ void LoadOrderHandler::SetLoadOrder(const std::vector<std::string>& loadOrder) c
throw std::system_error(ret, libloadorder_category(), err);
}
}
void LoadOrderHandler::BackupLoadOrder(const std::vector<std::string>& loadOrder,
const boost::filesystem::path & backupDirectory) {
const int maxBackupIndex = 2;
boost::format filenameFormat = boost::format("loadorder.bak.%1%");
boost::filesystem::path backupFilePath = backupDirectory / (filenameFormat % 2).str();
if (boost::filesystem::exists(backupFilePath))
boost::filesystem::remove(backupFilePath);
for (int i = maxBackupIndex - 1; i > -1; --i) {
const boost::filesystem::path backupFilePath = backupDirectory / (filenameFormat % i).str();
if (boost::filesystem::exists(backupFilePath))
boost::filesystem::rename(backupFilePath, backupDirectory / (filenameFormat % (i + 1)).str());
}
boost::filesystem::ofstream out(backupDirectory / (filenameFormat % 0).str());
for (const auto &plugin : loadOrder)
out << plugin << std::endl;
}
}
+3
View File
@@ -47,6 +47,9 @@ public:
bool IsPluginActive(const std::string& pluginName) const;
void SetLoadOrder(const std::vector<std::string>& loadOrder) const;
static void BackupLoadOrder(const std::vector<std::string>& loadOrder,
const boost::filesystem::path& backupDirectory);
private:
lo_game_handle gh_;
};
+141 -40
View File
@@ -33,7 +33,44 @@ namespace loot {
namespace test {
class LoadOrderHandlerTest : public CommonGameTestFixture {
protected:
LoadOrderHandlerTest() : loadOrderToSet_({
masterFile,
blankEsm,
blankMasterDependentEsm,
blankDifferentEsm,
blankDifferentMasterDependentEsm,
blankDifferentEsp,
blankDifferentPluginDependentEsp,
blankEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
}),
loadOrderBackupFile0(localPath / "loadorder.bak.0"),
loadOrderBackupFile1(localPath / "loadorder.bak.1"),
loadOrderBackupFile2(localPath / "loadorder.bak.2"),
loadOrderBackupFile3(localPath / "loadorder.bak.3") {}
void TearDown() {
CommonGameTestFixture::TearDown();
boost::filesystem::remove(loadOrderBackupFile0);
boost::filesystem::remove(loadOrderBackupFile1);
boost::filesystem::remove(loadOrderBackupFile2);
}
void initialiseHandler() {
GameSettings game(GetParam());
game.SetGamePath(dataPath.parent_path());
ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath));
}
LoadOrderHandler loadOrderHandler_;
std::vector<std::string> loadOrderToSet_;
const boost::filesystem::path loadOrderBackupFile0;
const boost::filesystem::path loadOrderBackupFile1;
const boost::filesystem::path loadOrderBackupFile2;
const boost::filesystem::path loadOrderBackupFile3;
};
// Pass an empty first argument, as it's a prefix for the test instantation,
@@ -78,9 +115,7 @@ TEST_P(LoadOrderHandlerTest, isPluginActiveShouldThrowIfTheHandlerHasNotBeenInit
}
TEST_P(LoadOrderHandlerTest, isPluginActiveShouldReturnCorrectPluginStatesAfterInitialisation) {
GameSettings game(GetParam());
game.SetGamePath(dataPath.parent_path());
ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath));
initialiseHandler();
EXPECT_TRUE(loadOrderHandler_.IsPluginActive(masterFile));
EXPECT_TRUE(loadOrderHandler_.IsPluginActive(blankEsm));
@@ -92,55 +127,121 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldThrowIfTheHandlerHasNotBeenInitia
}
TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
GameSettings game(GetParam());
game.SetGamePath(dataPath.parent_path());
ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath));
initialiseHandler();
ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder());
}
TEST_P(LoadOrderHandlerTest, setLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) {
std::vector<std::string> loadOrder({
masterFile,
blankEsm,
blankMasterDependentEsm,
blankDifferentEsm,
blankDifferentMasterDependentEsm,
blankDifferentEsp,
blankDifferentPluginDependentEsp,
blankEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
});
EXPECT_THROW(loadOrderHandler_.SetLoadOrder(loadOrder), std::system_error);
EXPECT_THROW(loadOrderHandler_.SetLoadOrder(loadOrderToSet_), std::system_error);
}
TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) {
GameSettings game(GetParam());
game.SetGamePath(dataPath.parent_path());
ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath));
initialiseHandler();
std::vector<std::string> loadOrder({
masterFile,
blankEsm,
blankMasterDependentEsm,
blankDifferentEsm,
blankDifferentMasterDependentEsm,
blankDifferentEsp,
blankDifferentPluginDependentEsp,
blankEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
});
EXPECT_NO_THROW(loadOrderHandler_.SetLoadOrder(loadOrder));
EXPECT_NO_THROW(loadOrderHandler_.SetLoadOrder(loadOrderToSet_));
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se)
loadOrder.erase(begin(loadOrder));
loadOrderToSet_.erase(begin(loadOrderToSet_));
EXPECT_EQ(loadOrder, getLoadOrder());
EXPECT_EQ(loadOrderToSet_, getLoadOrder());
}
TEST_P(LoadOrderHandlerTest, backupLoadOrderShouldCreateABackupOfTheCurrentLoadOrder) {
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile0));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile1));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile2));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile0));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile1));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile2));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
auto loadOrder = readFileLines(loadOrderBackupFile0);
EXPECT_EQ(loadOrderToSet_, loadOrder);
}
TEST_P(LoadOrderHandlerTest, backupLoadOrderShouldRollOverExistingBackups) {
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile0));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile1));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile2));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
auto firstSetLoadOrder = loadOrderToSet_;
ASSERT_NE(blankPluginDependentEsp, loadOrderToSet_[9]);
ASSERT_NE(blankDifferentMasterDependentEsp, loadOrderToSet_[10]);
loadOrderToSet_[9] = blankPluginDependentEsp;
loadOrderToSet_[10] = blankDifferentMasterDependentEsp;
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile0));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile1));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile2));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
auto loadOrder = readFileLines(loadOrderBackupFile0);
EXPECT_EQ(loadOrderToSet_, loadOrder);
loadOrder = readFileLines(loadOrderBackupFile1);
EXPECT_EQ(firstSetLoadOrder, loadOrder);
}
TEST_P(LoadOrderHandlerTest, backupLoadOrderShouldKeepUpToThreeBackups) {
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile0));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile1));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile2));
ASSERT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
auto firstSetLoadOrder = loadOrderToSet_;
ASSERT_NE(blankPluginDependentEsp, loadOrderToSet_[9]);
ASSERT_NE(blankDifferentMasterDependentEsp, loadOrderToSet_[10]);
loadOrderToSet_[9] = blankPluginDependentEsp;
loadOrderToSet_[10] = blankDifferentMasterDependentEsp;
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
auto secondSetLoadOrder = loadOrderToSet_;
ASSERT_NE(blankMasterDependentEsp, loadOrderToSet_[7]);
ASSERT_NE(blankEsp, loadOrderToSet_[8]);
loadOrderToSet_[7] = blankMasterDependentEsp;
loadOrderToSet_[8] = blankEsp;
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
auto thirdSetLoadOrder = loadOrderToSet_;
ASSERT_NE(blankMasterDependentEsm, loadOrderToSet_[7]);
ASSERT_NE(blankDifferentEsm, loadOrderToSet_[8]);
loadOrderToSet_[7] = blankMasterDependentEsm;
loadOrderToSet_[8] = blankDifferentEsm;
ASSERT_NO_THROW(LoadOrderHandler::BackupLoadOrder(loadOrderToSet_, localPath));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile0));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile1));
EXPECT_TRUE(boost::filesystem::exists(loadOrderBackupFile2));
EXPECT_FALSE(boost::filesystem::exists(loadOrderBackupFile3));
auto loadOrder = readFileLines(loadOrderBackupFile0);
EXPECT_EQ(loadOrderToSet_, loadOrder);
loadOrder = readFileLines(loadOrderBackupFile1);
EXPECT_EQ(thirdSetLoadOrder, loadOrder);
loadOrder = readFileLines(loadOrderBackupFile2);
EXPECT_EQ(secondSetLoadOrder, loadOrder);
}
}
}
+20 -11
View File
@@ -100,6 +100,22 @@ protected:
ASSERT_FALSE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost")));
}
std::vector<std::string> readFileLines(const boost::filesystem::path& file) {
boost::filesystem::ifstream in(file);
std::vector<std::string> lines;
while (in) {
std::string line;
std::getline(in, line);
if (!line.empty()) {
lines.push_back(line);
}
}
return lines;
}
std::vector<std::string> getLoadOrder() {
std::vector<std::string> actual;
if (isLoadOrderTimestampBased(GetParam())) {
@@ -125,17 +141,10 @@ protected:
actual.push_back(line);
}
} else {
boost::filesystem::ifstream in(localPath / "plugins.txt");
while (in) {
std::string line;
std::getline(in, line);
if (!line.empty()) {
if (line[0] == '*')
line = line.substr(1);
actual.push_back(line);
}
actual = readFileLines(localPath / "plugins.txt");
for (auto& line : actual) {
if (line[0] == '*')
line = line.substr(1);
}
}