← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/filesystem-errors into lp:widelands

 

Arty has proposed merging lp:~widelands-dev/widelands/filesystem-errors into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/filesystem-errors/+merge/358219
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/filesystem-errors into lp:widelands.
=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2018-09-25 06:32:35 +0000
+++ src/editor/editorinteractive.cc	2018-11-02 11:11:32 +0000
@@ -187,6 +187,7 @@
 	}
 
 	ml->load_map_complete(egbase(), Widelands::MapLoader::LoadType::kEditor);
+	egbase().postload();
 	egbase().load_graphics(loader_ui);
 	map_changed(MapWas::kReplaced);
 }

=== modified file 'src/editor/ui_menus/main_menu_save_map.cc'
--- src/editor/ui_menus/main_menu_save_map.cc	2018-06-01 08:50:29 +0000
+++ src/editor/ui_menus/main_menu_save_map.cc	2018-11-02 11:11:32 +0000
@@ -272,16 +272,14 @@
 		if (mbox.run<UI::Panel::Returncodes>() == UI::Panel::Returncodes::kBack)
 			return false;
 	}
-
-	// save to a tmp file/dir first, rename later
-	// (important to keep script files in the script directory)
-	const std::string tmp_name = complete_filename + ".tmp";
-	if (g_fs->file_exists(tmp_name)) {
+	
+	//  Try deleting file (if it exists). If it fails, give a message and let the player choose a new name.
+	try {
+		g_fs->fs_unlink(complete_filename);
+	} catch (const std::exception& e) {
 		const std::string s =
-		   (boost::format(
-		       _("A file with the name ‘%s.tmp’ already exists. You have to remove it manually.")) %
-		    FileSystem::fs_filename(filename.c_str()))
-		      .str();
+		   (boost::format(_("File ‘%s.tmp’ could not be deleted.")) % FileSystem::fs_filename(filename.c_str())).str()
+		   + " " + _("Try saving under a different name!");
 		UI::WLMessageBox mbox(&eia(), _("Error Saving Map!"), s, UI::WLMessageBox::MBoxType::kOk);
 		mbox.run<UI::Panel::Returncodes>();
 		return false;
@@ -290,51 +288,35 @@
 	Widelands::EditorGameBase& egbase = eia().egbase();
 	Widelands::Map* map = egbase.mutable_map();
 
-	{  // fs scope
+	// Recompute seafaring tag
+	map->cleanup_port_spaces(egbase.world());
+	if (map->allows_seafaring()) {
+		map->add_tag("seafaring");
+	} else {
+		map->delete_tag("seafaring");
+	}
+
+	if (map->has_artifacts()) {
+		map->add_tag("artifacts");
+	} else {
+		map->delete_tag("artifacts");
+	}
+	
+	//  Try saving.
+	try {
 		std::unique_ptr<FileSystem> fs(
-		   g_fs->create_sub_file_system(tmp_name, binary ? FileSystem::ZIP : FileSystem::DIR));
-
-		// Recompute seafaring tag
-		map->cleanup_port_spaces(egbase.world());
-		if (map->allows_seafaring()) {
-			map->add_tag("seafaring");
-		} else {
-			map->delete_tag("seafaring");
-		}
-
-		if (map->has_artifacts()) {
-			map->add_tag("artifacts");
-		} else {
-			map->delete_tag("artifacts");
-		}
-
-		try {
-			Widelands::MapSaver* wms = new Widelands::MapSaver(*fs, egbase);
-			wms->save();
-			delete wms;
-			// Reset filesystem to avoid file locks on saves
-			fs.reset();
-			map->reset_filesystem();
-			eia().set_need_save(false);
-			g_fs->fs_unlink(complete_filename);
-			g_fs->fs_rename(tmp_name, complete_filename);
-			// Also change fs, as we assign it to the map below
-			fs.reset(g_fs->make_sub_file_system(complete_filename));
-			// Set the filesystem of the map to the current save file / directory
-			map->swap_filesystem(fs);
-			// DONT use fs as of here, its garbage now!
-
-		} catch (const std::exception& e) {
-			std::string s = _("Error Saving Map!\nSaved map file may be corrupt!\n\nReason "
-			                  "given:\n");
-			s += e.what();
-			UI::WLMessageBox mbox(&eia(), _("Error Saving Map!"), s, UI::WLMessageBox::MBoxType::kOk);
-			mbox.run<UI::Panel::Returncodes>();
-
-			// cleanup tmp file if it was created
-			g_fs->fs_unlink(tmp_name);
-		}
-	}  // end fs scope, dont use it
+		   g_fs->create_sub_file_system(complete_filename, binary ? FileSystem::ZIP : FileSystem::DIR));
+		Widelands::MapSaver* wms = new Widelands::MapSaver(*fs, egbase);
+		wms->save();
+		delete wms;
+		fs.reset();
+	} catch (const std::exception& e) {
+		std::string s = _("Error Saving Map!\nSaved map file may be corrupt!\n\nReason "
+		                  "given:\n");
+		s += e.what();
+		UI::WLMessageBox mbox(&eia(), _("Error Saving Map!"), s, UI::WLMessageBox::MBoxType::kOk);
+		mbox.run<UI::Panel::Returncodes>();
+	}
 
 	die();
 	return true;

=== modified file 'src/io/filesystem/disk_filesystem.cc'
--- src/io/filesystem/disk_filesystem.cc	2018-04-07 16:59:00 +0000
+++ src/io/filesystem/disk_filesystem.cc	2018-11-02 11:11:32 +0000
@@ -167,15 +167,13 @@
 }
 
 /**
- * Create a sub filesystem out of this filesystem
+ * Make a sub filesystem out of this filesystem
  */
 FileSystem* RealFSImpl::make_sub_file_system(const std::string& path) {
 	FileSystemPath fspath(canonicalize_name(path));
 
 	if (!fspath.exists_) {
-		throw wexception("RealFSImpl: unable to create sub filesystem, path does not exist for '%s'"
-		                 " in directory '%s'",
-		                 fspath.c_str(), directory_.c_str());
+		throw FileNotFoundError("RealFSImpl::make_sub_file_system", fspath);
 	}
 
 	if (fspath.is_directory_)
@@ -190,10 +188,7 @@
 FileSystem* RealFSImpl::create_sub_file_system(const std::string& path, Type const fs) {
 	FileSystemPath fspath(canonicalize_name(path));
 	if (fspath.exists_)
-		throw wexception(
-		   "path '%s'' already exists in directory '%s', can not create a filesystem from it",
-		   path.c_str(), directory_.c_str());
-
+		throw FileError("RealFSImpl::create_sub_file_system", fspath, "path already exists, cannot create new filesystem from it");
 	if (fs == FileSystem::DIR) {
 		ensure_directory_exists(path);
 		return new RealFSImpl(fspath);
@@ -221,20 +216,22 @@
 void RealFSImpl::unlink_file(const std::string& file) {
 	FileSystemPath fspath(canonicalize_name(file));
 	if (!fspath.exists_) {
-		throw wexception(
-		   "RealFSImpl: unable to unlink file, path does not exist for '%s' in directory '%s'",
-		   fspath.c_str(), directory_.c_str());
+		throw FileNotFoundError("RealFSImpl::unlink_file", fspath);
 	}
 	if (fspath.is_directory_) {
-		throw wexception(
-		   "RealFSImpl: unable to unlink file, path '%s' in directory '%s' is a directory",
-		   fspath.c_str(), directory_.c_str());
+		throw FileTypeError("RealFSImpl::unlink_file", fspath, "path is a directory");
 	}
-
 #ifndef _WIN32
-	unlink(fspath.c_str());
+	if (unlink(fspath.c_str()) == -1)
+		throw FileError("RealFSImpl::unlink_file", fspath, strerror(errno));
 #else
-	DeleteFile(fspath.c_str());
+	// Note: We could possibly replace this with _unlink()
+	//       which would then also work with errno instead of GetLastError(),
+	//       but I am not sure if this is available (and works properly)
+	//       on all windows platforms or only with Visual Studio.
+	if (!DeleteFile(fspath.c_str()))
+		throw FileError("RealFSImpl::unlink_file", fspath, std::string("file error (Windows error code ")+std::to_string(GetLastError())+")");
+		// TODO: generate proper system message from GetLastError() via FormatMessage
 #endif
 }
 
@@ -244,14 +241,10 @@
 void RealFSImpl::unlink_directory(const std::string& file) {
 	FileSystemPath fspath(canonicalize_name(file));
 	if (!fspath.exists_) {
-		throw wexception("RealFSImpl: unable to unlink directory, path does not exist for '%s'"
-		                 " in directory '%s'",
-		                 fspath.c_str(), directory_.c_str());
+		throw FileNotFoundError("RealFSImpl::unlink_directory", fspath);
 	}
 	if (!fspath.is_directory_) {
-		throw wexception("RealFSImpl: unable to unlink directory, path '%s' in directory '%s'"
-		                 " is not a directory",
-		                 fspath.c_str(), directory_.c_str());
+		throw FileTypeError("RealFSImpl::unlink_directory", fspath, "path is not a directory");
 	}
 
 	FilenameSet files = list_directory(file);
@@ -268,14 +261,18 @@
 			unlink_file(*pname);
 	}
 
-// NOTE: this might fail if this directory contains CVS dir,
-// so no error checking here
+// NOTE: this might fail if this directory contains CVS dir
 #ifndef _WIN32
-	rmdir(fspath.c_str());
+	if (rmdir(fspath.c_str()) == -1)
+		throw FileError("RealFSImpl::unlink_directory", fspath, strerror(errno));
 #else
+	// Note: We could possibly replace this with _rmdir()
+	//       which would then also work with errno instead of GetLastError(),
+	//       but I am not sure if this is available (and works properly)
+	//       on all windows platforms or only with Visual Studio.
 	if (!RemoveDirectory(fspath.c_str()))
-		throw wexception(
-		   "'%s' could not be deleted in directory '%s'.", fspath.c_str(), directory_.c_str());
+		throw FileError("RealFSImpl::unlink_directory", fspath, std::string("file error (Windows error code ")+std::to_string(GetLastError())+")");
+		// TODO: generate proper system message from GetLastError() via FormatMessage
 #endif
 }
 
@@ -292,25 +289,20 @@
 	boost::replace_all(clean_dirname, "\\", "/");
 #endif
 
-	try {
-		std::string::size_type it = 0;
-		while (it < clean_dirname.size()) {
-			it = clean_dirname.find('/', it);
-
-			FileSystemPath fspath(canonicalize_name(clean_dirname.substr(0, it)));
-			if (fspath.exists_ && !fspath.is_directory_)
-				throw wexception("'%s' in directory '%s' exists and is not a directory",
-				                 clean_dirname.substr(0, it).c_str(), directory_.c_str());
-			if (!fspath.exists_)
-				make_directory(clean_dirname.substr(0, it));
-
-			if (it == std::string::npos)
-				break;
-			++it;
-		}
-	} catch (const std::exception& e) {
-		throw wexception("RealFSImpl::ensure_directory_exists(%s) in directory '%s': %s",
-		                 clean_dirname.c_str(), directory_.c_str(), e.what());
+	std::string::size_type it = 0;
+	while (it < clean_dirname.size()) {
+		it = clean_dirname.find('/', it);
+
+		FileSystemPath fspath(canonicalize_name(clean_dirname.substr(0, it)));
+		if (fspath.exists_ && !fspath.is_directory_)
+			throw FileTypeError("RealFSImpl::ensure_directory_exists",
+			   fspath, "path is not a directory");
+		if (!fspath.exists_)
+			make_directory(clean_dirname.substr(0, it));
+
+		if (it == std::string::npos)
+			break;
+		++it;
 	}
 }
 
@@ -318,33 +310,37 @@
  * Create this directory, throw an error if it already exists or
  * if a file is in the way or if the creation fails.
  *
- * Pleas note, this function does not honor parents,
+ * Please note, this function does not honor parents,
  * make_directory("onedir/otherdir/onemoredir") will fail
  * if either onedir or otherdir is missing
  */
 void RealFSImpl::make_directory(const std::string& dirname) {
 	FileSystemPath fspath(canonicalize_name(dirname));
 	if (fspath.exists_)
-		throw wexception("a file/directory with the name '%s' already exists in directory '%s'",
-		                 dirname.c_str(), directory_.c_str());
-
-	if
-#ifdef _WIN32
-	   (!CreateDirectory(fspath.c_str(), NULL))
+		throw FileError("RealFSImpl::make_directory", fspath, "a file or directory with that name already exists");
+	
+#ifndef _WIN32
+	if (!mkdir(fspath.c_str(), 0x1FF) == -1)
+		throw FileError("RealFSImpl::make_directory", fspath, strerror(errno));
 #else
-	   (mkdir(fspath.c_str(), 0x1FF) == -1)
+	// Note: We could possibly replace this with _mkdir()
+	//       which would then also work with errno instead of GetLastError(),
+	//       but I am not sure if this is available (and works properly)
+	//       on all windows platforms or only with Visual Studio.
+	if (!CreateDirectory(fspath.c_str(), NULL))
+		throw FileError("RealFSImpl::make_directory", fspath, std::string("file error (Windows error code ")+std::to_string(GetLastError())+")");
+		// TODO: generate proper system message from GetLastError() via FormatMessage
 #endif
-		throw DirectoryCannotCreateError("RealFSImpl::make_directory", dirname, strerror(errno));
 }
 
 /**
- * Read the given file into alloced memory; called by FileRead::open.
+ * Read the given file into allocated memory; called by FileRead::open.
  * Throws an exception if the file couldn't be opened.
  */
 void* RealFSImpl::load(const std::string& fname, size_t& length) {
 	const std::string fullname = canonicalize_name(fname);
 	if (is_directory(fullname)) {
-		throw FileError("RealFSImpl::load", fullname);
+		throw FileTypeError("RealFSImpl::load", fullname, "path is a directory");
 	}
 
 	FILE* file = nullptr;
@@ -353,7 +349,7 @@
 	try {
 		file = fopen(fullname.c_str(), "rb");
 		if (!file)
-			throw FileError("RealFSImpl::load", fullname);
+			throw FileError("RealFSImpl::load", fullname, "could not open file for reading");
 
 		// determine the size of the file (rather quirky, but it doesn't require
 		// potentially unportable functions)
@@ -371,6 +367,9 @@
 
 		// allocate a buffer and read the entire file into it
 		data = malloc(size + 1);  //  TODO(unknown): memory leak!
+		if (!data)
+			throw wexception("RealFSImpl::load: memory allocation failed for reading file %s (%s) with size %" PRIuS "",
+			                 fname.c_str(), fullname.c_str(), size);
 		int result = fread(data, size, 1, file);
 		if (size && (result != 1)) {
 			throw wexception("RealFSImpl::load: read failed for %s (%s) with size %" PRIuS "",
@@ -383,10 +382,10 @@
 
 		length = size;
 	} catch (...) {
-		if (file) {
+		if (file)
 			fclose(file);
-		}
-		free(data);
+		if (data)
+			free(data);
 		throw;
 	}
 
@@ -410,7 +409,7 @@
 
 	FILE* const f = fopen(fullname.c_str(), append ? "a" : "wb");
 	if (!f)
-		throw wexception("could not open %s (%s) for writing", fname.c_str(), fullname.c_str());
+		throw FileError("RealFSImpl::write", fullname, "could not open file for writing");
 
 	size_t const c = fwrite(data, length, 1, f);
 	fclose(f);
@@ -424,8 +423,7 @@
 	const std::string fullname1 = canonicalize_name(old_name);
 	const std::string fullname2 = canonicalize_name(new_name);
 	if (rename(fullname1.c_str(), fullname2.c_str()) != 0)
-		throw wexception("DiskFileSystem: unable to rename %s to %s: %s", fullname1.c_str(),
-		                 fullname2.c_str(), strerror(errno));
+		throw FileError("RealFSImpl::fs_rename", fullname1, std::string("unable to rename file to ") + fullname2 + ", " + strerror(errno));
 }
 
 /*****************************************************************************
@@ -439,7 +437,7 @@
 struct RealFSStreamRead : public StreamRead {
 	explicit RealFSStreamRead(const std::string& fname) : file_(fopen(fname.c_str(), "rb")) {
 		if (!file_)
-			throw wexception("could not open %s for reading", fname.c_str());
+			throw FileError("RealFSStreamRead::RealFSStreamRead", fname, "could not open file for reading");
 	}
 
 	~RealFSStreamRead() override {
@@ -477,7 +475,7 @@
 	explicit RealFSStreamWrite(const std::string& fname) : filename_(fname) {
 		file_ = fopen(fname.c_str(), "wb");
 		if (!file_)
-			throw wexception("could not open %s for writing", fname.c_str());
+			throw FileError("RealFSStreamWrite::RealFSStreamWrite", fname, "could not open file for writing");
 	}
 
 	~RealFSStreamWrite() override {

=== modified file 'src/io/filesystem/filesystem.cc'
--- src/io/filesystem/filesystem.cc	2018-07-12 04:41:20 +0000
+++ src/io/filesystem/filesystem.cc	2018-11-02 11:11:32 +0000
@@ -306,7 +306,7 @@
 	std::list<std::string>::iterator i;
 
 #ifdef _WIN32
-	// remove all slashes with backslashes so following can work.
+	// replace all slashes with backslashes so following can work.
 	for (uint32_t j = 0; j < path.size(); ++j) {
 		if (path[j] == '/')
 			path[j] = '\\';
@@ -463,7 +463,7 @@
 		fs.ensure_directory_exists(".widelands");
 		fs.fs_unlink(".widelands");
 		return true;
-	} catch (...) {
+	} catch (const FileError& e) {
 		log("Directory %s is not writeable - next try\n", path);
 	}
 

=== modified file 'src/io/filesystem/zip_filesystem.cc'
--- src/io/filesystem/zip_filesystem.cc	2018-04-07 16:59:00 +0000
+++ src/io/filesystem/zip_filesystem.cc	2018-11-02 11:11:32 +0000
@@ -262,15 +262,20 @@
  * Create a sub filesystem out of this filesystem
  */
 FileSystem* ZipFilesystem::make_sub_file_system(const std::string& path) {
+	if (path == ".") {
+		return new ZipFilesystem(zip_file_, basedir_in_zip_file_);
+	}
 	if (!file_exists(path)) {
 		throw wexception(
 		   "ZipFilesystem::make_sub_file_system: The path '%s' does not exist in zip file '%s'.",
-		   path.c_str(), zip_file_->path().c_str());
+		   (basedir_in_zip_file_.empty() ? path : basedir_in_zip_file_ + "/" + path).c_str(),
+		   zip_file_->path().c_str());
 	}
 	if (!is_directory(path)) {
-		throw wexception("ZipFilesystem::make_sub_file_system: "
-		                 "The path '%s' needs to be a directory in zip file '%s'.",
-		                 path.c_str(), zip_file_->path().c_str());
+		throw wexception(
+		   "ZipFilesystem::make_sub_file_system: The path '%s' needs to be a directory in zip file '%s'.",
+		   (basedir_in_zip_file_.empty()?path:basedir_in_zip_file_+"/"+path).c_str(),
+		   zip_file_->path().c_str());
 	}
 
 	std::string localpath = path;
@@ -288,7 +293,10 @@
 // see Filesystem::create
 FileSystem* ZipFilesystem::create_sub_file_system(const std::string& path, Type const type) {
 	if (file_exists(path)) {
-		throw wexception("ZipFilesystem::create_sub_file_system: Sub file system already exists.");
+		throw wexception(
+		   "ZipFilesystem::create_sub_file_system: Path '%s' already exists in zip file %s.",
+		   (basedir_in_zip_file_.empty() ? path : basedir_in_zip_file_ + "/" + path).c_str(),
+		   zip_file_->path().c_str());
 	}
 
 	if (type != FileSystem::DIR)
@@ -435,8 +443,9 @@
 		   "ZipFilesystem::write", complete_filename,
 		   (boost::format("in path '%s'', Error") % zip_file_->path() % strerror(errno)).str());
 	default:
-		throw FileError("ZipFilesystem::write", complete_filename,
-		                (boost::format("in path '%s'") % zip_file_->path()).str());
+		throw FileError(
+		   "ZipFilesystem::write", complete_filename,
+		   (boost::format("in path '%s'") % zip_file_->path()).str());
 	}
 
 	zipCloseFileInZip(zip_file_->write_handle());

=== modified file 'src/logic/editor_game_base.cc'
--- src/logic/editor_game_base.cc	2018-09-28 05:41:33 +0000
+++ src/logic/editor_game_base.cc	2018-11-02 11:11:32 +0000
@@ -26,10 +26,12 @@
 #include "base/i18n.h"
 #include "base/macros.h"
 #include "base/scoped_timer.h"
+#include "base/time_string.h"
 #include "base/wexception.h"
 #include "economy/flag.h"
 #include "economy/road.h"
 #include "graphic/color.h"
+#include "logic/filesystem_constants.h"
 #include "logic/findimmovable.h"
 #include "logic/game.h"
 #include "logic/game_data_error.h"
@@ -48,6 +50,7 @@
 #include "logic/player.h"
 #include "logic/playersmanager.h"
 #include "logic/roadtype.h"
+#include "map_io/map_saver.h"
 #include "scripting/logic.h"
 #include "scripting/lua_table.h"
 #include "ui_basic/progresswindow.h"
@@ -67,12 +70,86 @@
    : gametime_(0),
      lua_(lua_interface),
      player_manager_(new PlayersManager(*this)),
-     ibase_(nullptr) {
+     ibase_(nullptr),
+     tmp_fs_(nullptr) {
 	if (!lua_)  // TODO(SirVer): this is sooo ugly, I can't say
 		lua_.reset(new LuaEditorInterface(this));
 }
 
 EditorGameBase::~EditorGameBase() {
+	delete_tempfile();
+}
+
+/**
+ * deletes the temporary file/dir
+ * also resets the map filesystem if it points to the temporary file
+ */
+void EditorGameBase::delete_tempfile() {
+	if (!tmp_fs_)
+		return;
+	std::string fs_filename = tmp_fs_->get_basename();
+	std::string mapfs_filename = map_.filesystem()->get_basename();
+	if (mapfs_filename == fs_filename)
+		map_.reset_filesystem();
+	tmp_fs_.reset();
+	try {
+		g_fs->fs_unlink(fs_filename);
+	} catch (const std::exception& e) {
+		// if file deletion fails then we have an abaondoned file lying around, but otherwise that's unproblematic
+		log("EditorGameBase::delete_tempfile: deleting temporary file/dir failed: %s\n", e.what());
+	}
+}
+
+/**
+ * creates a new file/dir, saves the map data, and reassigns the map filesystem
+ * does not delete the former temp file if one exists
+ * throws an exception if something goes wrong
+ */
+void EditorGameBase::create_tempfile_and_save_mapdata(FileSystem::Type const type) {
+  // should only be called when a map was already loaded
+	assert(map_.filesystem());
+
+	g_fs->ensure_directory_exists(kTempFileDir);
+
+	std::string filename = kTempFileDir + g_fs->file_separator() + timestring() + "_mapdata";
+	std::string complete_filename = filename + kTempFileExtension;
+
+	// if a file with that name already exists, then try a few name modifications
+	if (g_fs->file_exists(complete_filename))
+	{
+		int suffix;
+		for (suffix = 0; suffix <= 9; suffix++)
+		{
+			complete_filename = filename + "-" + std::to_string(suffix) + ".tmp";
+			if (!g_fs->file_exists(complete_filename))
+			  break;
+		}
+		if (suffix > 9) {
+		  throw wexception("EditorGameBase::create_tempfile_and_save_mapdata(): for all considered filenames a file already existed");
+		}
+	}
+
+	// create tmp_fs_
+	tmp_fs_.reset(g_fs->create_sub_file_system(complete_filename, type));
+
+	// save necessary map data (we actually save the whole map)
+	Widelands::MapSaver* wms = new Widelands::MapSaver(*tmp_fs_, *this);
+	wms->save();
+	delete wms;
+	
+	// swap map fs
+	std::unique_ptr<FileSystem> mapfs(tmp_fs_->make_sub_file_system("."));
+	map_.swap_filesystem(mapfs);
+	mapfs.reset();
+
+	// This is just a convenience hack:
+	// If tmp_fs_ is a zip filesystem then - because of the way zip filesystems are currently implemented -
+	// the file is still in zip mode right now, which means that the file isn't finalized yet, i.e.,
+	// not even a valid zip file until zip mode ends. To force ending the zip mode (thus finalizing the file)
+	// we simply perform a (otherwise useless) filesystem request.
+	// It's not strictly necessary, but this way we get a valid zip file immediately istead of
+	// at some unkown later point (when an unzip operation happens or a filesystem object destructs).
+	tmp_fs_->file_exists("binary");
 }
 
 void EditorGameBase::think() {
@@ -196,6 +273,16 @@
  * graphics are loaded.
  */
 void EditorGameBase::postload() {
+	if (map_.filesystem()) {
+		// save map data to temporary file and reassign map fs
+		try {
+			create_tempfile_and_save_mapdata(FileSystem::ZIP);
+		} catch (const WException& e) {
+			log("EditorGameBase::postload: saving map to temporary file failed: %s", e.what());
+			throw;
+		}
+	}
+
 	// Postload tribes
 	assert(tribes_);
 	tribes_->postload();
@@ -411,6 +498,8 @@
 	player_manager_->cleanup();
 
 	map_.cleanup();
+
+	delete_tempfile();
 }
 
 void EditorGameBase::set_road(const FCoords& f, uint8_t const direction, uint8_t const roadtype) {

=== modified file 'src/logic/editor_game_base.h'
--- src/logic/editor_game_base.h	2018-04-07 16:59:00 +0000
+++ src/logic/editor_game_base.h	2018-11-02 11:11:32 +0000
@@ -254,6 +254,15 @@
 	std::unique_ptr<Tribes> tribes_;
 	std::unique_ptr<InteractiveBase> ibase_;
 	Map map_;
+	
+	/// Even after a map is fully loaded, some static data (images, scripts)
+	/// will still be read from a filesystem whenever a map/game is saved.
+	/// To avoid potential filesystem conflicts when (pre)loading/saving/deleting
+	/// map/game files (and to avoid having to deal with this in many different places)
+	/// a temporary file (in a special dir) is created for such data.
+	std::unique_ptr<FileSystem> tmp_fs_;
+	void delete_tempfile();
+	void create_tempfile_and_save_mapdata(FileSystem::Type const type);
 
 	DISALLOW_COPY_AND_ASSIGN(EditorGameBase);
 };

=== modified file 'src/logic/filesystem_constants.h'
--- src/logic/filesystem_constants.h	2018-04-22 16:01:32 +0000
+++ src/logic/filesystem_constants.h	2018-11-02 11:11:32 +0000
@@ -38,6 +38,13 @@
 const std::string kS2MapExtension1 = ".swd";
 const std::string kS2MapExtension2 = ".wld";
 
+/// Filesystem names for temp files holding static data that needs to be accessible via filesystem
+/// Kept in a separate dir to avoid filesystem conflicts
+const std::string kTempFileDir = "in_progress";
+const std::string kTempFileExtension = ".tmp";
+// We delete (accidentally remaining) temp files older than a week
+constexpr double kTempFilesKeepAroundTime = 7 * 24 * 60 * 60;
+
 /// Filesystem names and timeouts for replays
 const std::string kReplayDir = "replays";
 const std::string kReplayExtension = ".wrpl";

=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc	2018-10-09 17:09:34 +0000
+++ src/wlapplication.cc	2018-11-02 11:11:32 +0000
@@ -339,6 +339,7 @@
 	changedir_on_mac();
 	cleanup_replays();
 	cleanup_ai_files();
+	cleanup_temp_files();
 
 	Section& config = g_options.pull_section("global");
 
@@ -1498,6 +1499,21 @@
 	}
 }
 
+/**
+ * Delete old temp files that might still lurk around (game crashes etc.)
+ */
+void WLApplication::cleanup_temp_files() {
+	for (const std::string& filename :
+	     filter(g_fs->list_directory(kTempFileDir), [](const std::string& fn) {
+		     return boost::ends_with(fn, kTempFileExtension);
+		  })) {
+		if (is_autogenerated_and_expired(filename, kTempFilesKeepAroundTime)) {
+			log("Deleting old temp file: %s\n", filename.c_str());
+			g_fs->fs_unlink(filename);
+		}
+	}
+}
+
 bool WLApplication::redirect_output(std::string path) {
 	if (path.empty()) {
 #ifdef _WIN32

=== modified file 'src/wlapplication.h'
--- src/wlapplication.h	2018-04-07 16:59:00 +0000
+++ src/wlapplication.h	2018-11-02 11:11:32 +0000
@@ -216,6 +216,8 @@
 
 	void cleanup_ai_files();
 
+	void cleanup_temp_files();
+
 	bool redirect_output(std::string path = "");
 
 	// Handle the given pressed key. Returns true when key was


Follow ups