mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Add code to check integrity of backups
This commit is contained in:
parent
cc1e76490c
commit
6b1c884eb5
@ -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
|
||||
|
@ -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
|
||||
|
@ -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&) {
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
144
src/app/document_diff.cpp
Normal 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
45
src/app/document_diff.h
Normal 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
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user