mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Improve write/read stability of backup data
Now we backup 3 versions for each object. And when an object is saved correctly on disk, a "magic number" is saved at the beginning of the file. In this way we know that the file really represents the full object (and a crash was not occurred in the middle of the process).
This commit is contained in:
parent
8e6080af0b
commit
aeb1a799d7
63
src/app/crash/internals.h
Normal file
63
src/app/crash/internals.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
|
||||
#ifndef APP_CRASH_INTERNALS_H_INCLUDED
|
||||
#define APP_CRASH_INTERNALS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/object.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
namespace app {
|
||||
namespace crash {
|
||||
|
||||
const uint32_t MAGIC_NUMBER = 0x454E4946; // 'FINE' in ASCII
|
||||
|
||||
class ObjVersions {
|
||||
public:
|
||||
ObjVersions() {
|
||||
m_vers[0] = 0;
|
||||
m_vers[1] = 0;
|
||||
m_vers[2] = 0;
|
||||
}
|
||||
|
||||
size_t size() const { return 3; }
|
||||
doc::ObjectVersion operator[](int i) const { return m_vers[i]; }
|
||||
|
||||
doc::ObjectVersion newer() { return m_vers[0]; }
|
||||
doc::ObjectVersion older() { return m_vers[2]; }
|
||||
|
||||
// Adds a newer version
|
||||
void rotateRevisions(doc::ObjectVersion newer) {
|
||||
// Rotate versions
|
||||
m_vers[2] = m_vers[1];
|
||||
m_vers[1] = m_vers[0];
|
||||
m_vers[0] = newer;
|
||||
}
|
||||
|
||||
// Adds a version (we don't know if the version if the latest one)
|
||||
void add(doc::ObjectVersion ver) {
|
||||
auto minver = std::min_element(m_vers, m_vers+2);
|
||||
if (*minver < ver) {
|
||||
*minver = ver;
|
||||
std::sort(m_vers, m_vers+2, std::greater<doc::ObjectVersion>());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
doc::ObjectVersion m_vers[3];
|
||||
};
|
||||
|
||||
typedef std::map<doc::ObjectId, ObjVersions> ObjVersionsMap;
|
||||
|
||||
} // namespace crash
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -12,6 +12,7 @@
|
||||
#include "app/crash/read_document.h"
|
||||
|
||||
#include "app/console.h"
|
||||
#include "app/crash/internals.h"
|
||||
#include "app/document.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/exception.h"
|
||||
@ -59,7 +60,61 @@ class Reader : public SubObjectsIO {
|
||||
public:
|
||||
Reader(const std::string& dir)
|
||||
: m_sprite(nullptr)
|
||||
, m_dir(dir) {
|
||||
, m_dir(dir)
|
||||
, m_docId(0)
|
||||
, m_docVersions(nullptr)
|
||||
, m_loadInfo(nullptr) {
|
||||
for (const auto& fn : base::list_files(dir)) {
|
||||
auto i = fn.find('-');
|
||||
if (i == std::string::npos)
|
||||
continue; // Has no ID
|
||||
|
||||
auto j = fn.find('.', ++i);
|
||||
if (j == std::string::npos)
|
||||
continue; // Has no version
|
||||
|
||||
ObjectId id = base::convert_to<int>(fn.substr(i, j - i));
|
||||
ObjectVersion ver = base::convert_to<int>(fn.substr(j+1));
|
||||
if (!id || !ver)
|
||||
continue; // Error converting strings to ID/ver
|
||||
|
||||
ObjVersions& versions = m_objVersions[id];
|
||||
versions.add(ver);
|
||||
|
||||
if (fn.compare(0, 3, "doc") == 0) {
|
||||
if (!m_docId)
|
||||
m_docId = id;
|
||||
else {
|
||||
ASSERT(m_docId == id);
|
||||
}
|
||||
|
||||
m_docVersions = &versions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app::Document* loadDocument() {
|
||||
app::Document* doc = loadObject<app::Document*>("doc", m_docId, &Reader::readDocument);
|
||||
if (!doc)
|
||||
Console().printf("Error recovering the document\n");
|
||||
return doc;
|
||||
}
|
||||
|
||||
bool loadDocumentInfo(DocumentInfo& info) {
|
||||
m_loadInfo = &info;
|
||||
return
|
||||
loadObject<app::Document*>("doc", m_docId, &Reader::readDocument)
|
||||
== (app::Document*)1;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const ObjectVersion docId() const {
|
||||
return m_docId;
|
||||
}
|
||||
|
||||
const ObjVersions* docVersions() const {
|
||||
return m_docVersions;
|
||||
}
|
||||
|
||||
Sprite* loadSprite(ObjectId sprId) {
|
||||
@ -82,24 +137,61 @@ public:
|
||||
return m_celdatas[celdataId] = celData;
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
T loadObject(const char* prefix, ObjectId id, T (Reader::*readMember)(std::ifstream&)) {
|
||||
TRACE(" - Restoring %s%d\n", prefix, id);
|
||||
const ObjVersions& versions = m_objVersions[id];
|
||||
|
||||
IFSTREAM(m_dir, s, prefix + base::convert_to<std::string>(id));
|
||||
for (size_t i=0; i<versions.size(); ++i) {
|
||||
ObjectVersion ver = versions[i];
|
||||
if (!ver)
|
||||
continue;
|
||||
|
||||
T obj = nullptr;
|
||||
if (s.good())
|
||||
obj = (this->*readMember)(s);
|
||||
TRACE(" - Restoring %s #%d v%d\n", prefix, id, ver);
|
||||
|
||||
if (obj) {
|
||||
TRACE(" - %s%d restored successfully\n", prefix, id);
|
||||
return obj;
|
||||
std::string fn = prefix;
|
||||
fn.push_back('-');
|
||||
fn += base::convert_to<std::string>(id);
|
||||
fn.push_back('.');
|
||||
fn += base::convert_to<std::string>(ver);
|
||||
|
||||
IFSTREAM(m_dir, s, fn);
|
||||
T obj = nullptr;
|
||||
if (read32(s) == MAGIC_NUMBER)
|
||||
obj = (this->*readMember)(s);
|
||||
|
||||
if (obj) {
|
||||
TRACE(" - %s #%d v%d restored successfully\n", prefix, id, ver);
|
||||
return obj;
|
||||
}
|
||||
else {
|
||||
TRACE(" - %s #%d v%d was not restored\n", prefix, id, ver);
|
||||
if (!m_loadInfo)
|
||||
Console().printf("Error loading object %s #%d v%d\n", prefix, id, ver);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
app::Document* readDocument(std::ifstream& s) {
|
||||
ObjectId sprId = read32(s);
|
||||
std::string filename = read_string(s);
|
||||
|
||||
// Load DocumentInfo only
|
||||
if (m_loadInfo) {
|
||||
m_loadInfo->filename = filename;
|
||||
return (app::Document*)loadSprite(sprId);
|
||||
}
|
||||
|
||||
Sprite* spr = loadSprite(sprId);
|
||||
if (spr) {
|
||||
app::Document* doc = new app::Document(spr);
|
||||
doc->setFilename(filename);
|
||||
doc->impossibleToBackToSavedState();
|
||||
return doc;
|
||||
}
|
||||
else {
|
||||
TRACE(" - %s%d was not restored\n", prefix, id);
|
||||
Console().printf("Error loading object %s%d\n", prefix, id);
|
||||
Console().printf("Unable to load sprite #%d\n", sprId);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@ -109,25 +201,34 @@ private:
|
||||
int w = read16(s);
|
||||
int h = read16(s);
|
||||
color_t transparentColor = read32(s);
|
||||
frame_t nframes = read32(s);
|
||||
|
||||
if (format != IMAGE_RGB &&
|
||||
format != IMAGE_INDEXED &&
|
||||
format != IMAGE_GRAYSCALE) {
|
||||
Console().printf("Invalid sprite format #%d\n", (int)format);
|
||||
if (!m_loadInfo)
|
||||
Console().printf("Invalid sprite format #%d\n", (int)format);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (w < 1 || h < 1 || w > 0xfffff || h > 0xfffff) {
|
||||
Console().printf("Invalid sprite dimension %dx%d\n", w, h);
|
||||
if (!m_loadInfo)
|
||||
Console().printf("Invalid sprite dimension %dx%d\n", w, h);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_loadInfo) {
|
||||
m_loadInfo->format = format;
|
||||
m_loadInfo->width = w;
|
||||
m_loadInfo->height = w;
|
||||
m_loadInfo->frames = nframes;
|
||||
return (Sprite*)1;
|
||||
}
|
||||
|
||||
base::UniquePtr<Sprite> spr(new Sprite(format, w, h, 256));
|
||||
m_sprite = spr.get();
|
||||
spr->setTransparentColor(transparentColor);
|
||||
|
||||
// Frame durations
|
||||
frame_t nframes = read32(s);
|
||||
if (nframes >= 1) {
|
||||
spr->setTotalFrames(nframes);
|
||||
for (frame_t fr=0; fr<nframes; ++fr) {
|
||||
@ -214,6 +315,10 @@ private:
|
||||
|
||||
Sprite* m_sprite; // Used to pass the sprite in LayerImage() ctor
|
||||
std::string m_dir;
|
||||
ObjectVersion m_docId;
|
||||
ObjVersionsMap m_objVersions;
|
||||
ObjVersions* m_docVersions;
|
||||
DocumentInfo* m_loadInfo;
|
||||
std::map<ObjectId, ImageRef> m_images;
|
||||
std::map<ObjectId, CelDataRef> m_celdatas;
|
||||
};
|
||||
@ -225,46 +330,12 @@ private:
|
||||
|
||||
bool read_document_info(const std::string& dir, DocumentInfo& info)
|
||||
{
|
||||
ObjectId sprId;
|
||||
|
||||
if (base::is_file(base::join_path(dir, "doc"))) {
|
||||
IFSTREAM(dir, s, "doc");
|
||||
sprId = read32(s);
|
||||
info.filename = read_string(s);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
IFSTREAM(dir, s, "spr" + base::convert_to<std::string>(sprId));
|
||||
info.format = (PixelFormat)read8(s);
|
||||
info.width = read16(s);
|
||||
info.height = read16(s);
|
||||
read32(s); // Ignore transparent color
|
||||
info.frames = read32(s);
|
||||
return true;
|
||||
return Reader(dir).loadDocumentInfo(info);
|
||||
}
|
||||
|
||||
app::Document* read_document(const std::string& dir)
|
||||
{
|
||||
if (!base::is_file(base::join_path(dir, "doc")))
|
||||
return nullptr;
|
||||
|
||||
IFSTREAM(dir, s, "doc");
|
||||
ObjectId sprId = read32(s);
|
||||
std::string filename = read_string(s);
|
||||
|
||||
Reader reader(dir);
|
||||
Sprite* spr = reader.loadSprite(sprId);
|
||||
if (spr) {
|
||||
app::Document* doc = new app::Document(spr);
|
||||
doc->setFilename(filename);
|
||||
doc->impossibleToBackToSavedState();
|
||||
return doc;
|
||||
}
|
||||
else {
|
||||
Console().printf("Unable to load sprite #%d\n", sprId);
|
||||
return nullptr;
|
||||
}
|
||||
return Reader(dir).loadDocument();
|
||||
}
|
||||
|
||||
} // namespace crash
|
||||
|
@ -24,6 +24,13 @@ namespace crash {
|
||||
int height;
|
||||
doc::frame_t frames;
|
||||
std::string filename;
|
||||
|
||||
DocumentInfo() :
|
||||
format(doc::IMAGE_RGB),
|
||||
width(0),
|
||||
height(0),
|
||||
frames(0) {
|
||||
}
|
||||
};
|
||||
|
||||
bool read_document_info(const std::string& dir, DocumentInfo& info);
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/crash/write_document.h"
|
||||
|
||||
#include "app/crash/internals.h"
|
||||
#include "app/document.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
@ -43,34 +44,30 @@ using namespace base::serialization::little_endian;
|
||||
using namespace doc;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define OFSTREAM(dir, name, fn) \
|
||||
std::ofstream name(base::from_utf8(base::join_path(dir, fn)), std::ofstream::binary);
|
||||
#define OFSTREAM(name, fullfn) \
|
||||
std::ofstream name(base::from_utf8(fullfn), std::ofstream::binary);
|
||||
#else
|
||||
#define OFSTREAM(dir, name, fn) \
|
||||
std::ofstream name(base::join_path(dir, fn).c_str(), std::ofstream::binary);
|
||||
#define OFSTREAM(name, fullfn) \
|
||||
std::ofstream name(fullfn.c_str(), std::ofstream::binary);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
typedef std::map<ObjectId, ObjectVersion> Versions;
|
||||
static std::map<ObjectId, Versions> g_documentObjects;
|
||||
static std::map<ObjectId, ObjVersionsMap> g_docVersions;
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
Writer(const std::string& dir, app::Document* doc)
|
||||
: m_dir(dir)
|
||||
, m_doc(doc)
|
||||
, m_versions(g_documentObjects[doc->id()]) {
|
||||
, m_objVersions(g_docVersions[doc->id()]) {
|
||||
}
|
||||
|
||||
void saveDocument() {
|
||||
Sprite* spr = m_doc->sprite();
|
||||
|
||||
saveObject(nullptr, m_doc, &Writer::writeDocumentFile);
|
||||
saveObject("spr", spr, &Writer::writeSprite);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Create one stream for each object
|
||||
// Save from objects without children (e.g. images), to aggregated
|
||||
// objects (e.g. cels, layers, etc.)
|
||||
|
||||
for (Palette* pal : spr->getPalettes())
|
||||
saveObject("pal", pal, &Writer::writePalette);
|
||||
@ -78,19 +75,21 @@ public:
|
||||
for (FrameTag* frtag : spr->frameTags())
|
||||
saveObject("frtag", frtag, &Writer::writeFrameTag);
|
||||
|
||||
for (Cel* cel : spr->uniqueCels()) {
|
||||
saveObject("img", cel->image(), &Writer::writeImage);
|
||||
saveObject("celdata", cel->data(), &Writer::writeCelData);
|
||||
}
|
||||
|
||||
for (Cel* cel : spr->cels())
|
||||
saveObject("cel", cel, &Writer::writeCel);
|
||||
|
||||
std::vector<Layer*> layers;
|
||||
spr->getLayersList(layers);
|
||||
for (Layer* lay : layers)
|
||||
saveObject("lay", lay, &Writer::writeLayerStructure);
|
||||
|
||||
for (Cel* cel : spr->cels())
|
||||
saveObject("cel", cel, &Writer::writeCel);
|
||||
|
||||
// Images (CelData)
|
||||
for (Cel* cel : spr->uniqueCels()) {
|
||||
saveObject("celdata", cel->data(), &Writer::writeCelData);
|
||||
saveObject("img", cel->image(), &Writer::writeImage);
|
||||
}
|
||||
saveObject("spr", spr, &Writer::writeSprite);
|
||||
saveObject("doc", m_doc, &Writer::writeDocumentFile);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -167,25 +166,44 @@ private:
|
||||
if (!obj->version())
|
||||
obj->incrementVersion();
|
||||
|
||||
if (m_versions[obj->id()] != obj->version()) {
|
||||
std::string fn = (prefix ? prefix + base::convert_to<std::string>(obj->id()): "doc");
|
||||
ObjVersions& versions = m_objVersions[obj->id()];
|
||||
if (versions.newer() == obj->version())
|
||||
return;
|
||||
|
||||
OFSTREAM(m_dir, s, fn);
|
||||
(this->*writeMember)(s, obj);
|
||||
m_versions[obj->id()] = obj->version();
|
||||
std::string fn = prefix;
|
||||
fn.push_back('-');
|
||||
fn += base::convert_to<std::string>(obj->id());
|
||||
|
||||
TRACE(" - %s %d saved with version %d\n",
|
||||
prefix, obj->id(), obj->version());
|
||||
std::string fullfn = base::join_path(m_dir, fn);
|
||||
std::string oldfn = fullfn + "." + base::convert_to<std::string>(versions.older());
|
||||
fullfn += "." + base::convert_to<std::string>(obj->version());
|
||||
|
||||
OFSTREAM(s, fullfn);
|
||||
write32(s, 0); // Leave a room for the magic number
|
||||
(this->*writeMember)(s, obj); // Write the object
|
||||
|
||||
// Write the magic number
|
||||
s.seekp(0);
|
||||
write32(s, MAGIC_NUMBER);
|
||||
|
||||
// Remove the older version
|
||||
try {
|
||||
if (versions.older() && base::is_file(oldfn))
|
||||
base::delete_file(oldfn);
|
||||
}
|
||||
else {
|
||||
TRACE(" - Ignoring %s %d (version %d already saved)\n",
|
||||
prefix, obj->id(), obj->version());
|
||||
catch (const std::exception&) {
|
||||
TRACE(" - Cannot delete %s #%d v%d\n", prefix, obj->id(), versions.older());
|
||||
}
|
||||
|
||||
// Rotate versions and add the latest one
|
||||
versions.rotateRevisions(obj->version());
|
||||
|
||||
TRACE(" - Saved %s #%d v%d\n", prefix, obj->id(), obj->version());
|
||||
}
|
||||
|
||||
std::string m_dir;
|
||||
app::Document* m_doc;
|
||||
Versions& m_versions;
|
||||
ObjVersionsMap& m_objVersions;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
@ -202,12 +220,12 @@ void write_document(const std::string& dir, app::Document* doc)
|
||||
void delete_document_internals(app::Document* doc)
|
||||
{
|
||||
ASSERT(doc);
|
||||
auto it = g_documentObjects.find(doc->id());
|
||||
auto it = g_docVersions.find(doc->id());
|
||||
|
||||
// The document could not be inside g_documentObjects in case it was
|
||||
// never saved by the backup process.
|
||||
if (it != g_documentObjects.end())
|
||||
g_documentObjects.erase(it);
|
||||
if (it != g_docVersions.end())
|
||||
g_docVersions.erase(it);
|
||||
}
|
||||
|
||||
} // namespace crash
|
||||
|
Loading…
x
Reference in New Issue
Block a user