From 6b1c884eb5b2720b8e4db02ed901bc24784d9ed3 Mon Sep 17 00:00:00 2001 From: David Capello <davidcapello@gmail.com> Date: Wed, 4 Jul 2018 12:35:15 -0300 Subject: [PATCH] Add code to check integrity of backups --- src/app/CMakeLists.txt | 1 + src/app/console.cpp | 6 +- src/app/crash/backup_observer.cpp | 42 ++++++++- src/app/crash/session.cpp | 31 +++++-- src/app/crash/session.h | 4 +- src/app/document.cpp | 2 +- src/app/document_diff.cpp | 144 ++++++++++++++++++++++++++++++ src/app/document_diff.h | 45 ++++++++++ src/ui/manager.cpp | 20 ++++- src/ui/message.h | 12 +++ src/ui/message_type.h | 3 +- src/ui/system.cpp | 13 +++ src/ui/system.h | 2 + src/ui/widget.cpp | 4 + 14 files changed, 315 insertions(+), 14 deletions(-) create mode 100644 src/app/document_diff.cpp create mode 100644 src/app/document_diff.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index b93aa9bb6..2fc1326c7 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 diff --git a/src/app/console.cpp b/src/app/console.cpp index 2da2c8ef4..5673e4874 100644 --- a/src/app/console.cpp +++ b/src/app/console.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 diff --git a/src/app/crash/backup_observer.cpp b/src/app/crash/backup_observer.cpp index 83063d096..4509ce9e0 100644 --- a/src/app/crash/backup_observer.cpp +++ b/src/app/crash/backup_observer.cpp @@ -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&) { diff --git a/src/app/crash/session.cpp b/src/app/crash/session.cpp index 5f372f33b..2252cb69a 100644 --- a/src/app/crash/session.cpp +++ b/src/app/crash/session.cpp @@ -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) diff --git a/src/app/crash/session.h b/src/app/crash/session.h index 2016a5800..f7553a92d 100644 --- a/src/app/crash/session.h +++ b/src/app/crash/session.h @@ -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; diff --git a/src/app/document.cpp b/src/app/document.cpp index 4d963c0a7..8c87ea1d6 100644 --- a/src/app/document.cpp +++ b/src/app/document.cpp @@ -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(); diff --git a/src/app/document_diff.cpp b/src/app/document_diff.cpp new file mode 100644 index 000000000..196ff85aa --- /dev/null +++ b/src/app/document_diff.cpp @@ -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 diff --git a/src/app/document_diff.h b/src/app/document_diff.h new file mode 100644 index 000000000..9c8c592cf --- /dev/null +++ b/src/app/document_diff.h @@ -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 diff --git a/src/ui/manager.cpp b/src/ui/manager.cpp index 2e492adbe..23d99ab41 100644 --- a/src/ui/manager.cpp +++ b/src/ui/manager.cpp @@ -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", diff --git a/src/ui/message.h b/src/ui/message.h index f777f31e4..61337eba9 100644 --- a/src/ui/message.h +++ b/src/ui/message.h @@ -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, diff --git a/src/ui/message_type.h b/src/ui/message_type.h index 12f7e3fe2..3d4760fd5 100644 --- a/src/ui/message_type.h +++ b/src/ui/message_type.h @@ -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. diff --git a/src/ui/system.cpp b/src/ui/system.cpp index d92010872..94e2edd88 100644 --- a/src/ui/system.cpp +++ b/src/ui/system.cpp @@ -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()); diff --git a/src/ui/system.h b/src/ui/system.h index d9a6a9f60..5bc4071dd 100644 --- a/src/ui/system.h +++ b/src/ui/system.h @@ -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(); diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index c3445709e..7ab64bee7 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -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: