From 3e6ae692ff8087ab89f757d50ed21a17eb50b130 Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Sun, 6 Jun 2010 13:34:49 +0100 Subject: [PATCH] Gracefully handle crashes when writing files Don't clobber maps until finished --- include/IResource.hpp | 25 +++++++++++++------ src/Map.cpp | 58 ++++++++++++++++++++++++++++--------------- src/Resource.cpp | 45 ++++++++++++++++++++++++--------- 3 files changed, 88 insertions(+), 40 deletions(-) diff --git a/include/IResource.hpp b/include/IResource.hpp index 99ac516..f5aa26d 100644 --- a/include/IResource.hpp +++ b/include/IResource.hpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2009 Nick Gasson +// Copyright (C) 2009-2010 Nick Gasson // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -40,16 +40,25 @@ struct IResource { public: enum Mode { READ, WRITE }; - explicit Handle(const string& aFileName, Mode aMode = READ); + explicit Handle(const string& fileName, Mode mode = READ); + ~Handle() { commit(); } - ifstream& rstream() { return *myReadStream; } - ofstream& wstream() { return *myWriteStream; } + ifstream& rstream() { return *readStream; } + ofstream& wstream() { return *writeStream; } - string fileName() const { return myFileName; } + string fileName() const { return fileName_; } + Mode mode() const { return mode_; } + + void commit(); + void rollback(); private: - shared_ptr myReadStream; - shared_ptr myWriteStream; - const string myFileName; + string tmpFileName() const; + + shared_ptr readStream; + shared_ptr writeStream; + const string fileName_; + const Mode mode_; + bool aborted; }; virtual Handle openFile(const string& aName) = 0; diff --git a/src/Map.cpp b/src/Map.cpp index 5db420f..f3013be 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -190,6 +190,7 @@ private: } void writeHeightMap() const; + void saveTo(ostream& of); void readHeightMap(IResource::Handle aHandle); void tileVertices(int x, int y, int* indexes) const; void renderPickSector(Point botLeft, Point topRight); @@ -1353,16 +1354,22 @@ void Map::writeHeightMap() const log() << "Writing terrain height map to " << h.fileName(); - ofstream& of = h.wstream(); - - const int32_t wl = static_cast(myWidth); - const int32_t dl = static_cast(myDepth); - of.write(reinterpret_cast(&wl), sizeof(int32_t)); - of.write(reinterpret_cast(&dl), sizeof(int32_t)); - - for (int i = 0; i < (myWidth + 1) * (myDepth + 1); i++) - of.write(reinterpret_cast(&heightMap[i].pos.y), - sizeof(float)); + try { + ofstream& of = h.wstream(); + + const int32_t wl = static_cast(myWidth); + const int32_t dl = static_cast(myDepth); + of.write(reinterpret_cast(&wl), sizeof(int32_t)); + of.write(reinterpret_cast(&dl), sizeof(int32_t)); + + for (int i = 0; i < (myWidth + 1) * (myDepth + 1); i++) + of.write(reinterpret_cast(&heightMap[i].pos.y), + sizeof(float)); + } + catch (std::exception& e) { + h.rollback(); + throw e; + } } // Read the height data back out of a binary file @@ -1396,17 +1403,8 @@ void Map::readHeightMap(IResource::Handle aHandle) } } -// Turn the map into XML -void Map::save() +void Map::saveTo(ostream& of) { - using namespace boost::filesystem; - - IResource::Handle h = resource->writeFile(resource->name() + ".xml"); - - log() << "Saving map to " << h.fileName(); - - ofstream& of = h.wstream(); - xml::element root("map"); root.addAttribute("width", myWidth); root.addAttribute("height", myDepth); @@ -1488,6 +1486,26 @@ void Map::save() of << xml::document(root); } +// Turn the map into XML +void Map::save() +{ + using namespace boost::filesystem; + + IResource::Handle h = resource->writeFile(resource->name() + ".xml"); + + log() << "Saving map to " << h.fileName(); + + ofstream& of = h.wstream(); + + try { + saveTo(of); + } + catch (exception& e) { + h.rollback(); + throw e; + } +} + IMapPtr makeEmptyMap(const string& aResId, int aWidth, int aDepth) { IResourcePtr res = makeNewResource(aResId, "maps"); diff --git a/src/Resource.cpp b/src/Resource.cpp index ae2a803..b799c34 100644 --- a/src/Resource.cpp +++ b/src/Resource.cpp @@ -1,5 +1,5 @@ // -// Copyright (C) 2009 Nick Gasson +// Copyright (C) 2009-2010 Nick Gasson // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ public: { } - + // IResource interface string name() const { return myPath.filename(); } string xmlFileName() const @@ -55,25 +55,46 @@ private: const path myPath; }; -IResource::Handle::Handle(const string& aFileName, Mode aMode) - : myFileName(aFileName) +IResource::Handle::Handle(const string& fileName, Mode mode) + : fileName_(fileName), mode_(mode), aborted(false) { - if (aMode == READ) { - myReadStream = shared_ptr(new ifstream(aFileName.c_str())); + if (mode == READ) { + readStream = shared_ptr(new ifstream(fileName.c_str())); - if (!myReadStream->good()) - throw runtime_error("Failed to open resource file " + aFileName); + if (!readStream->good()) + throw runtime_error("Failed to open resource file " + fileName); } - else if (aMode == WRITE) { - myWriteStream = shared_ptr(new ofstream(aFileName.c_str())); + else if (mode == WRITE) { + const string tmp = tmpFileName(); + writeStream = shared_ptr(new ofstream(tmp.c_str())); - if (!myWriteStream->good()) - throw runtime_error("Failed to open resource file " + aFileName); + if (!writeStream->good()) + throw runtime_error("Failed to open resource file " + fileName); } else throw runtime_error("Bad mode for Handle"); } +string IResource::Handle::tmpFileName() const +{ + return fileName() + ".tmp"; +} + +void IResource::Handle::rollback() +{ + remove(tmpFileName()); + aborted = true; +} + +void IResource::Handle::commit() +{ + if (mode() == WRITE && !aborted) { + remove(fileName()); + rename(tmpFileName(), fileName()); + remove(tmpFileName()); + } +} + namespace { const char* classes[] = { "maps", "buildings", "engines", "waggons", "trees", -- 2.39.2