diff --git a/src/app/crash/internals.h b/src/app/crash/internals.h new file mode 100644 index 000000000..acabaadd4 --- /dev/null +++ b/src/app/crash/internals.h @@ -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 +#include +#include + +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()); + } + } + + private: + doc::ObjectVersion m_vers[3]; + }; + + typedef std::map ObjVersionsMap; + +} // namespace crash +} // namespace app + +#endif diff --git a/src/app/crash/read_document.cpp b/src/app/crash/read_document.cpp index 04cfa255d..6adc7d9fa 100644 --- a/src/app/crash/read_document.cpp +++ b/src/app/crash/read_document.cpp @@ -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(fn.substr(i, j - i)); + ObjectVersion ver = base::convert_to(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("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("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 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(id)); + for (size_t i=0; i*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(id); + fn.push_back('.'); + fn += base::convert_to(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 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 m_images; std::map 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(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 diff --git a/src/app/crash/read_document.h b/src/app/crash/read_document.h index a48613868..41122cd40 100644 --- a/src/app/crash/read_document.h +++ b/src/app/crash/read_document.h @@ -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); diff --git a/src/app/crash/write_document.cpp b/src/app/crash/write_document.cpp index 37f247c84..23d2d3985 100644 --- a/src/app/crash/write_document.cpp +++ b/src/app/crash/write_document.cpp @@ -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 Versions; -static std::map g_documentObjects; +static std::map 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 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(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(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(versions.older()); + fullfn += "." + base::convert_to(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