mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
@@ -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
|
||||
------
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user