Improved masterlist update handling.

If the local repo was already at the latest masterlist, parsing gets
skipped (nb. update needs UI feedback). If a fast-forward update isn't
possible, the repo is deleted and re-cloned. Closes #240, #241.
This commit is contained in:
WrinklyNinja
2014-08-22 23:58:17 +01:00
parent 816384444d
commit ecc2166ec6
5 changed files with 88 additions and 63 deletions
+3
View File
@@ -322,6 +322,9 @@ function openReadme(evt) {
}
function updateMasterlist(evt) {
loot.query('updateMasterlist').then(JSON.parse).then(function(result){
if (result == null) {
return;
}
/* Update JS variables. */
loot.game.masterlist.revision = result.masterlist.revision;
+2 -2
View File
@@ -169,9 +169,9 @@ namespace loot {
// Masterlist member functions
//----------------------------
void Masterlist::Load(Game& game, const unsigned int language) {
bool Masterlist::Load(Game& game, const unsigned int language) {
try {
Update(game, language);
return Update(game, language);
}
catch (error& e) {
if (e.code() != error::ok) {
+2 -2
View File
@@ -70,8 +70,8 @@ namespace loot {
class Masterlist : public MetadataList {
public:
void Load(Game& game, const unsigned int language); //Handles update with load fallback.
void Update(Game& game, const unsigned int language);
bool Load(Game& game, const unsigned int language); //Handles update with load fallback.
bool Update(Game& game, const unsigned int language);
std::string GetRevision(const boost::filesystem::path& path);
std::string GetDate(const boost::filesystem::path& path);
+24 -7
View File
@@ -100,6 +100,15 @@ namespace loot {
return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0;
}
// Removes the read-only flag from some files in git repositories created by libgit2.
void FixRepoPermissions(const fs::path& path) {
BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path;
for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) {
BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path();
fs::permissions(it->path(), fs::add_perms | fs::owner_write);
}
}
int diffFileCallback(const git_diff_delta *delta, float progress, void * payload) {
BOOST_LOG_TRIVIAL(trace) << "Checking diff for: " << delta->old_file.path;
if (strcmp(delta->old_file.path, "masterlist.yaml") == 0) {
@@ -166,7 +175,7 @@ namespace loot {
}
}
void Masterlist::Update(Game& game, const unsigned int language) {
bool Masterlist::Update(Game& game, const unsigned int language) {
git_handler git;
fs::path repo_path = game.MasterlistPath().parent_path();
string repo_branch = game.RepoBranch();
@@ -200,11 +209,7 @@ namespace loot {
// seem to be made that way with no detrimental effect on libgit2's operation, but it
// stuffs these filesystem commands up.
if (fs::exists(temp_path)) {
BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << temp_path;
for (fs::recursive_directory_iterator it(temp_path); it != fs::recursive_directory_iterator(); ++it) {
BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path();
fs::permissions(it->path(), fs::add_perms | fs::owner_write);
}
FixRepoPermissions(temp_path);
fs::remove_all(temp_path);
}
fs::rename(repo_path, temp_path);
@@ -347,10 +352,20 @@ namespace loot {
git.ref2 = nullptr;
}
else if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) {
// No update necessary, so exit early to skip unnecessary masterlist parsing.
BOOST_LOG_TRIVIAL(trace) << "Local branch is up-to-date with remote branch.";
BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD.";
git.call(git_checkout_head(git.repo, &checkout_opts));
return false;
}
else {
throw error(error::git_error, "Local repository has been edited, an automatic fast-forward merge update is not possible.");
// The local repository can't be easily merged. It's best just to delete and re-clone it.
FixRepoPermissions(repo_path / ".git");
fs::remove_all(repo_path / ".git");
return this->Update(game, language);
//throw error(error::git_error, "Local repository has been edited, an automatic fast-forward merge update is not possible.");
}
}
@@ -448,5 +463,7 @@ namespace loot {
if (!parsingError.empty())
throw error(error::ok, parsingError); //Throw an OK because the process still completed in a successful state.
return true;
}
}
+57 -52
View File
@@ -697,81 +697,86 @@ namespace loot {
BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name();
// Update / parse masterlist.
bool wasChanged = true;
try {
g_app_state.CurrentGame().masterlist.Load(g_app_state.CurrentGame(), language);
wasChanged = g_app_state.CurrentGame().masterlist.Load(g_app_state.CurrentGame(), language);
}
catch (loot::error &e) {
if (e.code() == loot::error::ok) {
// There was a parsing error, but roll-back was successful, so the process
// should still complete.
parsingError = e.what();
wasChanged = true;
}
else
throw e;
}
// Now regenerate the JS-side masterlist data.
// Now regenerate the JS-side masterlist data if the masterlist was changed.
if (wasChanged) {
// The data structure is to be set as 'loot.game'.
YAML::Node gameNode;
// The data structure is to be set as 'loot.game'.
YAML::Node gameNode;
// Store the masterlist revision and date.
gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath());
gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath());
// Store the masterlist revision and date.
gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath());
gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath());
for (const auto& pluginPair : g_app_state.CurrentGame().plugins) {
Plugin mlistPlugin(pluginPair.second);
mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(pluginPair.second.Name()));
for (const auto& pluginPair : g_app_state.CurrentGame().plugins) {
Plugin mlistPlugin(pluginPair.second);
mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(pluginPair.second.Name()));
YAML::Node pluginNode;
if (!mlistPlugin.HasNameOnly()) {
// Now add the masterlist metadata to the pluginNode.
pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter();
pluginNode["masterlist"]["req"] = mlistPlugin.Reqs();
pluginNode["masterlist"]["inc"] = mlistPlugin.Incs();
pluginNode["masterlist"]["msg"] = mlistPlugin.Messages();
pluginNode["masterlist"]["tag"] = mlistPlugin.Tags();
pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo();
}
YAML::Node pluginNode;
if (!mlistPlugin.HasNameOnly()) {
// Now add the masterlist metadata to the pluginNode.
pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter();
pluginNode["masterlist"]["req"] = mlistPlugin.Reqs();
pluginNode["masterlist"]["inc"] = mlistPlugin.Incs();
pluginNode["masterlist"]["msg"] = mlistPlugin.Messages();
pluginNode["masterlist"]["tag"] = mlistPlugin.Tags();
pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo();
// Now merge masterlist and userlist metadata and evaluate,
// putting any resulting metadata into the base of the pluginNode.
YAML::Node derivedNode = GenerateDerivedMetadata(pluginPair.second.Name());
for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) {
const string key = it->first.as<string>();
pluginNode[key] = it->second;
}
gameNode["plugins"].push_back(pluginNode);
}
// Now merge masterlist and userlist metadata and evaluate,
// putting any resulting metadata into the base of the pluginNode.
YAML::Node derivedNode = GenerateDerivedMetadata(pluginPair.second.Name());
for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) {
const string key = it->first.as<string>();
pluginNode[key] = it->second;
//Evaluate any conditions in the global messages.
BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions.";
list<Message> messages = g_app_state.CurrentGame().masterlist.messages;
try {
list<Message>::iterator it = messages.begin();
while (it != messages.end()) {
if (!it->EvalCondition(g_app_state.CurrentGame(), language))
it = messages.erase(it);
else
++it;
}
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what();
messages.push_back(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str()));
}
gameNode["plugins"].push_back(pluginNode);
}
//Evaluate any conditions in the global messages.
BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions.";
list<Message> messages = g_app_state.CurrentGame().masterlist.messages;
try {
list<Message>::iterator it = messages.begin();
while (it != messages.end()) {
if (!it->EvalCondition(g_app_state.CurrentGame(), language))
it = messages.erase(it);
else
++it;
// Add the parsing error to the global messages, if it exists.
if (!parsingError.empty()) {
messages.push_back(Message(Message::error, parsingError));
}
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what();
messages.push_back(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str()));
}
// Add the parsing error to the global messages, if it exists.
if (!parsingError.empty()) {
messages.push_back(Message(Message::error, parsingError));
// Now store global messages from masterlist.
gameNode["globalMessages"] = messages;
return JSON::stringify(gameNode);
}
// Now store global messages from masterlist.
gameNode["globalMessages"] = messages;
return JSON::stringify(gameNode);
else
return "null";
}
std::string Handler::ClearAllMetadata() {