Add code to check integrity of backups

This commit is contained in:
David Capello 2018-07-04 12:35:15 -03:00
parent cc1e76490c
commit 6b1c884eb5
14 changed files with 315 additions and 14 deletions

View File

@ -498,6 +498,7 @@ add_library(app-lib
context_flags.cpp
document.cpp
document_api.cpp
document_diff.cpp
document_exporter.cpp
document_range.cpp
document_range_ops.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -22,6 +22,7 @@
#include "app/context.h"
#include "app/modules/gui.h"
#include "app/ui/status_bar.h"
#include "ui/system.h"
namespace app {
@ -39,6 +40,9 @@ static bool has_text = false;
Console::Console(Context* ctx)
: m_withUI(false)
{
if (!ui::is_ui_thread())
return;
if (ctx)
m_withUI = (ctx->isUIAvailable());
else

View File

@ -4,6 +4,13 @@
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
// Uncomment if you want to test the backup process each 5 secondsh
//#define TEST_BACKUPS_WITH_A_SHORT_PERIOD
// Uncomment if you want to check that backups are correctly saved
// after being saved.
//#define TEST_BACKUP_INTEGRITY
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@ -13,6 +20,8 @@
#include "app/app.h"
#include "app/crash/session.h"
#include "app/document.h"
#include "app/document_access.h"
#include "app/document_diff.h"
#include "app/pref/preferences.h"
#include "base/bind.h"
#include "base/chrono.h"
@ -20,6 +29,10 @@
#include "base/scoped_lock.h"
#include "doc/context.h"
#ifdef TEST_BACKUP_INTEGRITY
#include "ui/system.h"
#endif
namespace app {
namespace crash {
@ -84,7 +97,7 @@ void BackupObserver::backgroundThread()
{
int normalPeriod = int(60.0*Preferences::instance().general.dataRecoveryPeriod());
int lockedPeriod = 5;
#if 0 // Just for testing purposes
#ifdef TEST_BACKUPS_WITH_A_SHORT_PERIOD
normalPeriod = 5;
lockedPeriod = 5;
#endif
@ -113,6 +126,33 @@ void BackupObserver::backgroundThread()
TRACE("RECO: Document '%d' backup was canceled by UI\n", doc->id());
somethingLocked = true;
}
#ifdef TEST_BACKUP_INTEGRITY
else {
DocumentReader reader(doc, 500);
std::unique_ptr<app::Document> copy(
m_session->restoreBackupDocById(doc->id()));
DocumentDiff diff = compare_docs(doc, copy.get());
if (diff.anything) {
TRACE("RECO: Differences (%s/%s/%s/%s/%s/%s/%s)\n",
diff.canvas ? "canvas": "",
diff.totalFrames ? "totalFrames": "",
diff.frameDuration ? "frameDuration": "",
diff.frameTags ? "frameTags": "",
diff.palettes ? "palettes": "",
diff.layers ? "layers": "",
diff.cels ? "cels": "");
app::Document* copyDoc = copy.release();
ui::execute_from_ui_thread(
[this, copyDoc] {
m_ctx->documents().add(copyDoc);
});
}
else {
TRACE("RECO: No differences\n");
}
}
#endif
}
}
catch (const std::exception&) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -189,19 +189,27 @@ void Session::removeDocument(app::Document* doc)
}
}
void Session::restoreBackup(Backup* backup)
app::Document* Session::restoreBackupDoc(const std::string& backupDir)
{
Console console;
try {
app::Document* doc = read_document(backup->dir());
app::Document* doc = read_document(backupDir);
if (doc) {
fixFilename(doc);
UIContext::instance()->documents().add(doc);
return doc;
}
}
catch (const std::exception& ex) {
Console::showException(ex);
}
return nullptr;
}
void Session::restoreBackup(Backup* backup)
{
app::Document* doc = restoreBackupDoc(backup->dir());
if (doc)
UIContext::instance()->documents().add(doc);
}
void Session::restoreBackupById(const ObjectId id)
@ -210,9 +218,18 @@ void Session::restoreBackupById(const ObjectId id)
if (!base::is_directory(docDir))
return;
base::UniquePtr<Backup> backup(new Backup(docDir));
if (backup)
restoreBackup(backup.get());
app::Document* doc = restoreBackupDoc(docDir);
if (doc)
UIContext::instance()->documents().add(doc);
}
app::Document* Session::restoreBackupDocById(const doc::ObjectId id)
{
std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
if (!base::is_directory(docDir))
return nullptr;
return restoreBackupDoc(docDir);
}
void Session::restoreRawImages(Backup* backup, RawImagesAs as)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -55,10 +55,12 @@ namespace crash {
void restoreBackup(Backup* backup);
void restoreBackupById(const doc::ObjectId id);
app::Document* restoreBackupDocById(const doc::ObjectId id);
void restoreRawImages(Backup* backup, RawImagesAs as);
void deleteBackup(Backup* backup);
private:
app::Document* restoreBackupDoc(const std::string& backupDir);
void loadPid();
std::string pidFilename() const;
std::string verFilename() const;

View File

@ -439,7 +439,7 @@ Document* Document::duplicate(DuplicateType type) const
// Copy only some flags
documentCopy->m_flags = (m_flags & kMaskVisible);
documentCopy->setFilename(filename());
documentCopy->setMask(mask());
documentCopy->generateMaskBoundaries();

144
src/app/document_diff.cpp Normal file
View File

@ -0,0 +1,144 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/document_diff.h"
#include "app/document.h"
#include "doc/cel.h"
#include "doc/frame_tag.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
namespace app {
DocumentDiff compare_docs(const Document* a,
const Document* b)
{
DocumentDiff diff;
// Don't compare filenames
//if (a->filename() != b->filename())...
// Compare sprite specs
if (a->sprite()->width() != b->sprite()->width() ||
a->sprite()->height() != b->sprite()->height() ||
a->sprite()->pixelFormat() != b->sprite()->pixelFormat()) {
diff.anything = diff.canvas = true;
}
// Frames layers
if (a->sprite()->totalFrames() != b->sprite()->totalFrames()) {
diff.anything = diff.totalFrames = true;
}
else {
for (frame_t f=0; f<a->sprite()->totalFrames(); ++f) {
if (a->sprite()->frameDuration(f) != b->sprite()->frameDuration(f)) {
diff.anything = diff.frameDuration = true;
break;
}
}
}
// Tags
if (a->sprite()->frameTags().size() != b->sprite()->frameTags().size()) {
diff.anything = diff.frameTags = true;
}
else {
auto aIt = a->sprite()->frameTags().begin(), aEnd = a->sprite()->frameTags().end();
auto bIt = b->sprite()->frameTags().begin(), bEnd = b->sprite()->frameTags().end();
for (; aIt != aEnd && bIt != bEnd; ++aIt, ++bIt) {
const FrameTag* aTag = *aIt;
const FrameTag* bTag = *bIt;
if (aTag->fromFrame() != bTag->fromFrame() ||
aTag->toFrame() != bTag->toFrame() ||
aTag->name() != bTag->name() ||
aTag->color() != bTag->color() ||
aTag->aniDir() != bTag->aniDir()) {
diff.anything = diff.frameTags = true;
}
}
}
// Palettes layers
if (a->sprite()->getPalettes().size() != b->sprite()->getPalettes().size()) {
const PalettesList& aPals = a->sprite()->getPalettes();
const PalettesList& bPals = b->sprite()->getPalettes();
auto aIt = aPals.begin(), aEnd = aPals.end();
auto bIt = bPals.begin(), bEnd = bPals.end();
for (; aIt != aEnd && bIt != bEnd; ++aIt, ++bIt) {
const Palette* aPal = *aIt;
const Palette* bPal = *bIt;
if (aPal->countDiff(bPal, nullptr, nullptr)) {
diff.anything = diff.palettes = true;
break;
}
}
}
// Compare layers
if (a->sprite()->allLayersCount() != b->sprite()->allLayersCount()) {
diff.anything = diff.layers = true;
}
else {
LayerList aLayers = a->sprite()->allLayers();
LayerList bLayers = b->sprite()->allLayers();
auto aIt = aLayers.begin(), aEnd = aLayers.end();
auto bIt = bLayers.begin(), bEnd = bLayers.end();
for (; aIt != aEnd && bIt != bEnd; ++aIt, ++bIt) {
const Layer* aLay = *aIt;
const Layer* bLay = *bIt;
if (aLay->type() != bLay->type() ||
aLay->name() != bLay->name() ||
aLay->flags() != bLay->flags() ||
(aLay->isImage() && bLay->isImage() &&
(((const LayerImage*)aLay)->opacity() != ((const LayerImage*)bLay)->opacity()))) {
diff.anything = diff.layers = true;
break;
}
if (diff.totalFrames) {
for (frame_t f=0; f<a->sprite()->totalFrames(); ++f) {
const Cel* aCel = aLay->cel(f);
const Cel* bCel = bLay->cel(f);
if ((!aCel && bCel) ||
(aCel && !bCel)) {
diff.anything = diff.cels = true;
}
else if (aCel && bCel) {
if (aCel->frame() == bCel->frame() ||
aCel->bounds() == bCel->bounds() ||
aCel->opacity() == bCel->opacity()) {
diff.anything = diff.cels = true;
}
if (aCel->image() && bCel->image()) {
if (aCel->image()->bounds() != bCel->image()->bounds() ||
count_diff_between_images(aCel->image(), bCel->image()))
diff.anything = diff.images = true;
}
else if (aCel->image() != bCel->image())
diff.anything = diff.images = true;
}
}
}
}
}
return diff;
}
} // namespace app

45
src/app/document_diff.h Normal file
View File

@ -0,0 +1,45 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_DOCUMENT_DIFF_H_INCLUDED
#define APP_DOCUMENT_DIFF_H_INCLUDED
#pragma once
namespace app {
class Document;
struct DocumentDiff {
bool anything : 1;
bool canvas : 1;
bool totalFrames : 1;
bool frameDuration : 1;
bool frameTags : 1;
bool palettes : 1;
bool layers : 1;
bool cels : 1;
bool images : 1;
DocumentDiff() :
anything(false),
canvas(false),
totalFrames(false),
frameDuration(false),
frameTags(false),
palettes(false),
layers(false),
cels(false),
images(false) {
}
};
// Useful for testing purposes to detect if two documents (after
// some kind of operation) are equivalent.
DocumentDiff compare_docs(const Document* a,
const Document* b);
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -16,6 +16,7 @@
#include "ui/manager.h"
#include "base/concurrent_queue.h"
#include "base/scoped_value.h"
#include "base/time.h"
#include "she/display.h"
@ -72,6 +73,7 @@ static base::thread::native_handle_type manager_thread = 0;
static WidgetsList mouse_widgets_list; // List of widgets to send mouse events
static Messages msg_queue; // Messages queue
static base::concurrent_queue<Message*> concurrent_msg_queue;
static Filters msg_filters[NFILTERS]; // Filters for every enqueued message
static int filter_locks = 0;
@ -264,6 +266,14 @@ bool Manager::generateMessages()
if (children().empty())
return false;
// Generate messages from other threads
if (!concurrent_msg_queue.empty()) {
Message* msg = nullptr;
while (concurrent_msg_queue.try_pop(msg))
msg_queue.push_back(msg);
}
// Generate messages from OS input
generateMessagesFromSheEvents();
// Generate messages for timers
@ -643,7 +653,11 @@ void Manager::addToGarbage(Widget* widget)
void Manager::enqueueMessage(Message* msg)
{
ASSERT(msg);
msg_queue.push_back(msg);
if (is_ui_thread())
msg_queue.push_back(msg);
else
concurrent_msg_queue.push(msg);
}
Window* Manager::getTopWindow()
@ -1414,6 +1428,8 @@ bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
#ifdef REPORT_EVENTS
{
static const char* msg_name[] = {
"kFunctionMessage",
"kOpenMessage",
"kCloseMessage",
"kCloseDisplayMessage",

View File

@ -18,6 +18,8 @@
#include "ui/pointer_type.h"
#include "ui/widgets_list.h"
#include <functional>
namespace ui {
class Timer;
@ -82,6 +84,16 @@ namespace ui {
KeyModifiers m_modifiers; // Key modifiers pressed when message was created
};
class FunctionMessage : public Message {
public:
FunctionMessage(std::function<void()>&& f)
: Message(kFunctionMessage),
m_f(std::move(f)) { }
void call() { m_f(); }
private:
std::function<void()> m_f;
};
class KeyMessage : public Message {
public:
KeyMessage(MessageType type,

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,6 +13,7 @@ namespace ui {
// Message types.
enum MessageType {
// General messages.
kFunctionMessage, // Call a function from the UI thread.
kOpenMessage, // Windows is open.
kCloseMessage, // Windows is closed.
kCloseDisplayMessage, // The user wants to close the entire application.

View File

@ -20,6 +20,7 @@
#include "ui/intern.h"
#include "ui/intern.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/overlay.h"
#include "ui/overlay_manager.h"
#include "ui/scale.h"
@ -346,6 +347,18 @@ void set_mouse_position(const gfx::Point& newPos)
_internal_set_mouse_position(newPos);
}
void execute_from_ui_thread(std::function<void()>&& f)
{
ASSERT(Manager::getDefault());
Manager* man = Manager::getDefault();
ASSERT(man);
FunctionMessage* msg = new FunctionMessage(std::move(f));
msg->addRecipient(man);
man->enqueueMessage(msg);
}
bool is_ui_thread()
{
return (main_gui_thread == base::this_thread::native_handle());

View File

@ -13,6 +13,7 @@
#include "ui/cursor_type.h"
#include "ui/mouse_buttons.h"
#include <functional>
#include <string>
namespace she { class Display; }
@ -69,6 +70,7 @@ namespace ui {
const gfx::Point& get_mouse_position();
void set_mouse_position(const gfx::Point& newPos);
void execute_from_ui_thread(std::function<void()>&& f);
bool is_ui_thread();
#ifdef _DEBUG
void assert_ui_thread();

View File

@ -1406,6 +1406,10 @@ bool Widget::onProcessMessage(Message* msg)
switch (msg->type()) {
case kFunctionMessage:
static_cast<FunctionMessage*>(msg)->call();
break;
case kOpenMessage:
case kCloseMessage:
case kWinMoveMessage: