From af74371f6d67f0f0acc57e894004d944742b3854 Mon Sep 17 00:00:00 2001
From: Thomas Goyne <plorkyeran@aegisub.org>
Date: Tue, 2 Jul 2013 19:49:45 -0700
Subject: [PATCH] Retry commits of file writes for up to a second to work
 around AV scanning

Poorly-written antivirus software briefly lock newly written files to
scan them for viruses, which makes the rename from the temp file to
actual file fail. Work around this by retrying the rename up to ten
times.

Closes #1620.
---
 aegisub/libaegisub/common/io.cpp             | 13 ++++++++++++-
 aegisub/libaegisub/include/libaegisub/util.h |  5 +++++
 aegisub/libaegisub/unix/util.cpp             |  6 ++++++
 aegisub/libaegisub/windows/util_win.cpp      |  6 ++++++
 aegisub/src/subtitles_provider_libass.cpp    |  9 ++-------
 5 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/aegisub/libaegisub/common/io.cpp b/aegisub/libaegisub/common/io.cpp
index db5728daf..2baac82c0 100644
--- a/aegisub/libaegisub/common/io.cpp
+++ b/aegisub/libaegisub/common/io.cpp
@@ -64,7 +64,18 @@ Save::Save(fs::path const& file, bool binary)
 
 Save::~Save() {
 	fp->close(); // Need to close before rename on Windows to unlock the file
-	fs::Rename(tmp_name, file_name);
+	for (int i = 0; i < 10; ++i) {
+		try {
+			fs::Rename(tmp_name, file_name);
+			return;
+		}
+		catch (agi::fs::FileSystemError const&) {
+			// Retry up to ten times in case it's just locked by a poorly-written antivirus scanner
+			if (i == 9)
+				throw;
+			util::sleep_for(100);
+		}
+	}
 }
 
 	} // namespace io
diff --git a/aegisub/libaegisub/include/libaegisub/util.h b/aegisub/libaegisub/include/libaegisub/util.h
index 602522617..26dd2970d 100644
--- a/aegisub/libaegisub/include/libaegisub/util.h
+++ b/aegisub/libaegisub/include/libaegisub/util.h
@@ -70,5 +70,10 @@ namespace agi {
 	}
 #endif
 
+	/// A thin wrapper around this_thread::sleep_for that uses std::thread on
+	/// Windows (to avoid having to compile boost.thread) and boost::thread
+	/// elsewhere (because libstcc++ 4.7 is missing it).
+	void sleep_for(int ms);
+
 	} // namespace util
 } // namespace agi
diff --git a/aegisub/libaegisub/unix/util.cpp b/aegisub/libaegisub/unix/util.cpp
index 65c4a7c3a..c7d62701f 100644
--- a/aegisub/libaegisub/unix/util.cpp
+++ b/aegisub/libaegisub/unix/util.cpp
@@ -20,6 +20,8 @@
 
 #include "libaegisub/util.h"
 
+#include <boost/thread.hpp>
+
 namespace agi { namespace util {
 
 timeval time_log() {
@@ -30,4 +32,8 @@ timeval time_log() {
 
 void SetThreadName(const char *) { }
 
+void sleep_for(int ms) {
+	boost::this_thread::sleep_for(boost::chrono::milliseconds(ms));
+}
+
 } }
diff --git a/aegisub/libaegisub/windows/util_win.cpp b/aegisub/libaegisub/windows/util_win.cpp
index 07283bd7f..dbac2819d 100644
--- a/aegisub/libaegisub/windows/util_win.cpp
+++ b/aegisub/libaegisub/windows/util_win.cpp
@@ -23,6 +23,8 @@
 
 #include "libaegisub/charset_conv_win.h"
 
+#include <thread>
+
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 
@@ -99,5 +101,9 @@ void SetThreadName(LPCSTR szThreadName) {
 	__except (EXCEPTION_CONTINUE_EXECUTION) {}
 }
 
+void sleep_for(int ms) {
+	std::this_thread::sleep_for(std::chrono::milliseconds(ms));
+}
+
 	} // namespace io
 } // namespace agi
diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp
index 8f9922f58..700b4004d 100644
--- a/aegisub/src/subtitles_provider_libass.cpp
+++ b/aegisub/src/subtitles_provider_libass.cpp
@@ -50,13 +50,12 @@
 
 #include <libaegisub/dispatch.h>
 #include <libaegisub/log.h>
+#include <libaegisub/util.h>
 
 #include <boost/gil/gil_all.hpp>
 #include <boost/range/algorithm_ext/push_back.hpp>
-#include <boost/thread.hpp>
 #include <memory>
 #include <mutex>
-#include <thread>
 
 namespace {
 std::unique_ptr<agi::dispatch::Queue> cache_queue;
@@ -104,11 +103,7 @@ LibassSubtitlesProvider::LibassSubtitlesProvider(std::string)
 	progress.Run([=](agi::ProgressSink *ps) {
 		ps->SetIndeterminate();
 		while (!*done && !ps->IsCancelled())
-#ifdef _MSC_VER
-			std::this_thread::sleep_for(std::chrono::milliseconds(250));
-#else
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(250));
-#endif
+			agi::util::sleep_for(250);
 	});
 
 	ass_renderer = *renderer;
-- 
GitLab