mirror of
https://github.com/aseprite/aseprite.git
synced 2024-12-29 00:23:48 +00:00
Add "Recover Files..." option in Home tab
We've changed the way the "recover files" option works: * Now it's an option that is always available (so we can open files even from sessions that were correctly closed in the past) * We can open sessions from other Aseprite versions (as in a "best effort" approach, if it works, ok, if it doesn't -> contact user support)
This commit is contained in:
parent
0902fa7629
commit
1b62515cd2
@ -638,7 +638,6 @@
|
||||
<background color="check_hot_face" state="selected" />
|
||||
<icon part="pal_options" />
|
||||
</style>
|
||||
<style id="recover_sprites_button" extends="button" border="0" padding="8" />
|
||||
<style id="new_frame_button" extends="mini_button" />
|
||||
<style id="color_button" extends="mini_button" border="5" font="mini" />
|
||||
<style id="splitter">
|
||||
@ -657,6 +656,10 @@
|
||||
<background color="background" />
|
||||
<background-border part="separator_horz" align="middle" />
|
||||
</style>
|
||||
<style id="separator_in_view_reverse">
|
||||
<background color="workspace" />
|
||||
<text color="background" x="4" align="left middle" />
|
||||
</style>
|
||||
<style id="vertical_separator" border-left="4" border-top="2" border-right="1" border-bottom="2">
|
||||
<background-border part="separator_vert" align="center" />
|
||||
</style>
|
||||
|
@ -132,6 +132,8 @@
|
||||
<option id="expand_menubar_on_mouseover" type="bool" default="false" migrate="Options.ExpandMenuBarOnMouseover" />
|
||||
<option id="data_recovery" type="bool" default="true" />
|
||||
<option id="data_recovery_period" type="double" default="2.0" />
|
||||
<option id="keep_edited_sprite_data" type="bool" default="true" />
|
||||
<option id="keep_edited_sprite_data_lifespan" type="int" default="7" />
|
||||
<option id="show_full_path" type="bool" default="true" />
|
||||
<option id="timeline_position" type="TimelinePosition" default="TimelinePosition::BOTTOM" />
|
||||
<option id="timeline_layer_panel_width" type="int" default="100" />
|
||||
|
@ -136,6 +136,7 @@ Aseprite
|
||||
||&OK
|
||||
END
|
||||
restart_by_preferences_save_recovery_data_period = Automatically save recovery data every X minutes
|
||||
restart_by_preferences_keep_edited_sprite_data_lifespan = Keep edited sprite data for X days
|
||||
restore_all_shortcuts = <<<END
|
||||
Warning
|
||||
<<Do you want to restore all keyboard shortcuts
|
||||
@ -619,9 +620,14 @@ height = Height:
|
||||
|
||||
[home_view]
|
||||
title = Home
|
||||
recover = Recover Lost Sprites
|
||||
new_file = New File...
|
||||
open_file = Open File...
|
||||
recover_files = Recover Files...
|
||||
recover_files_tooltip = <<<END
|
||||
Recover files from crashed sessions or
|
||||
closed sprite that were not saved in
|
||||
previous sessions.
|
||||
END
|
||||
recent_files = Recent files:
|
||||
recent_folders = Recent folders:
|
||||
news = News:
|
||||
@ -939,6 +945,18 @@ END
|
||||
10_minutes = 10 Minutes
|
||||
15_minutes = 15 Minutes
|
||||
30_minutes = 30 Minutes
|
||||
keep_edited_sprite_data = Keep edited sprite data for
|
||||
keep_edited_sprite_data_tooltip = <<<END
|
||||
With this option you can re-open edited documents
|
||||
after closing the program for the number of specified
|
||||
days.
|
||||
END
|
||||
1_day = 1 Day
|
||||
2_days = 2 Days
|
||||
3_days = 3 Days
|
||||
1_week = 1 Week
|
||||
2_weeks = 2 Weeks
|
||||
1_month = 1 Month
|
||||
show_full_path = Show full file name path
|
||||
show_full_path_tooltip = <<<END
|
||||
Uncheck this option if you would prefer to hide
|
||||
@ -1177,6 +1195,18 @@ color = Color:
|
||||
antialias = Anti-aliasing filter
|
||||
antialias_tooltip = Smooth font edges
|
||||
|
||||
[recover_files]
|
||||
title = Recover Files
|
||||
recover_sprite = Recover Sprite
|
||||
recover_n_sprites = Recover {} Sprite(s)
|
||||
delete = Delete
|
||||
refresh = Refresh
|
||||
raw_images_as_frames = Raw Images as Frames
|
||||
raw_images_as_layers = Raw Images as Layers
|
||||
crash_sessions = Crashed Sessions
|
||||
old_sessions = Previous Sessions
|
||||
incompatible = [MIGHT BE INCOMPATIBLE v{1}] {0}
|
||||
|
||||
[replace_color]
|
||||
from = From:
|
||||
to = To:
|
||||
|
@ -1,18 +1,16 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2001-2017 by David Capello -->
|
||||
<!-- Copyright (C) 2019 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2001-2017 David Capello -->
|
||||
<gui>
|
||||
<vbox noborders="true" id="home_view" border="4" childspacing="2" expansive="true">
|
||||
<hbox noborders="true" id="recover_sprites_placeholder">
|
||||
<boxfiller />
|
||||
<button id="recover_sprites" text="@.recover" style="recover_sprites_button" />
|
||||
<boxfiller />
|
||||
</hbox>
|
||||
|
||||
<hbox noborders="true" id="header_placeholder">
|
||||
<link id="aseprite_face" style="aseprite_face" />
|
||||
<vbox border="4" childspacing="4">
|
||||
<link id="new_file" text="@.new_file" style="workspace_link" />
|
||||
<link id="open_file" text="@.open_file" style="workspace_link" />
|
||||
<link id="recover_sprites" text="@.recover_files" style="workspace_link"
|
||||
tooltip="@.recover_files_tooltip" />
|
||||
</vbox>
|
||||
<boxfiller />
|
||||
<vbox border="4">
|
||||
|
@ -67,7 +67,7 @@
|
||||
text="@.color_bar_entries_separator"
|
||||
tooltip="@.color_bar_entries_separator"
|
||||
pref="color_bar.entries_separator" />
|
||||
<hbox>
|
||||
<grid columns="2">
|
||||
<check id="enable_data_recovery"
|
||||
text="@.auto_save_recovery_data"
|
||||
tooltip="@.auto_save_recovery_data_tooltip" />
|
||||
@ -81,7 +81,18 @@
|
||||
<listitem text="@.15_minutes" value="15" />
|
||||
<listitem text="@.30_minutes" value="30" />
|
||||
</combobox>
|
||||
</hbox>
|
||||
<check id="keep_edited_sprite_data"
|
||||
text="@.keep_edited_sprite_data"
|
||||
tooltip="@.keep_edited_sprite_data_tooltip" />
|
||||
<combobox id="keep_edited_sprite_data_lifespan">
|
||||
<listitem text="@.1_day" value="1" />
|
||||
<listitem text="@.2_days" value="2" />
|
||||
<listitem text="@.3_days" value="3" />
|
||||
<listitem text="@.1_week" value="7" />
|
||||
<listitem text="@.2_weeks" value="14" />
|
||||
<listitem text="@.1_month" value="30" />
|
||||
</combobox>
|
||||
</grid>
|
||||
<separator horizontal="true" />
|
||||
<link id="locate_file" text="@.locate_file" />
|
||||
<link id="locate_crash_folder" text="@.locate_crash_folder" />
|
||||
|
@ -137,7 +137,9 @@ public:
|
||||
InputChain m_inputChain;
|
||||
clipboard::ClipboardManager m_clipboardManager;
|
||||
#endif
|
||||
// This is a raw pointer because we want to delete this explicitly.
|
||||
// This is a raw pointer because we want to delete it explicitly.
|
||||
// (e.g. if an exception occurs, the ~Modules() doesn't have to
|
||||
// delete m_recovery)
|
||||
app::crash::DataRecovery* m_recovery;
|
||||
|
||||
Modules(const bool createLogInDesktop,
|
||||
@ -151,22 +153,40 @@ public:
|
||||
, m_recovery(nullptr) {
|
||||
}
|
||||
|
||||
app::crash::DataRecovery* recovery() {
|
||||
return m_recovery;
|
||||
~Modules() {
|
||||
ASSERT(m_recovery == nullptr);
|
||||
}
|
||||
|
||||
bool hasRecoverySessions() const {
|
||||
return m_recovery && !m_recovery->sessions().empty();
|
||||
app::crash::DataRecovery* recovery() {
|
||||
return m_recovery;
|
||||
}
|
||||
|
||||
void createDataRecovery() {
|
||||
#ifdef ENABLE_DATA_RECOVERY
|
||||
m_recovery = new app::crash::DataRecovery(&m_context);
|
||||
m_recovery->SessionsListIsReady.connect(
|
||||
[] {
|
||||
ui::assert_ui_thread();
|
||||
auto app = App::instance();
|
||||
if (app && app->mainWindow()) {
|
||||
// Notify that the list of sessions is ready.
|
||||
app->mainWindow()->dataRecoverySessionsAreReady();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void searchDataRecoverySessions() {
|
||||
#ifdef ENABLE_DATA_RECOVERY
|
||||
ASSERT(m_recovery);
|
||||
if (m_recovery)
|
||||
m_recovery->launchSearch();
|
||||
#endif
|
||||
}
|
||||
|
||||
void deleteDataRecovery() {
|
||||
#ifdef ENABLE_DATA_RECOVERY
|
||||
ASSERT(m_recovery);
|
||||
delete m_recovery;
|
||||
m_recovery = nullptr;
|
||||
#endif
|
||||
@ -269,14 +289,14 @@ void App::initialize(const AppOptions& options)
|
||||
if (m_mod)
|
||||
m_mod->modMainWindow(m_mainWindow.get());
|
||||
|
||||
// Data recovery is enabled only in GUI mode
|
||||
if (preferences().general.dataRecovery())
|
||||
m_modules->searchDataRecoverySessions();
|
||||
|
||||
// Default status of the main window.
|
||||
app_rebuild_documents_tabs();
|
||||
app_default_statusbar_message();
|
||||
|
||||
// Recover data
|
||||
if (m_modules->hasRecoverySessions())
|
||||
m_mainWindow->showDataRecovery(m_modules->recovery());
|
||||
|
||||
m_mainWindow->openWindow();
|
||||
|
||||
// Redraw the whole screen.
|
||||
@ -409,12 +429,12 @@ void App::run()
|
||||
if (isGui()) {
|
||||
// Destroy the window.
|
||||
m_mainWindow.reset(NULL);
|
||||
|
||||
// Delete backups (this is a normal shutdown, we are not handling
|
||||
// exceptions, and we are not in a destructor).
|
||||
m_modules->deleteDataRecovery();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Delete backups (this is a normal shutdown, we are not handling
|
||||
// exceptions, and we are not in a destructor).
|
||||
m_modules->deleteDataRecovery();
|
||||
}
|
||||
|
||||
// Finishes the Aseprite application.
|
||||
|
@ -242,6 +242,9 @@ public:
|
||||
if (m_pref.general.dataRecovery())
|
||||
enableDataRecovery()->setSelected(true);
|
||||
|
||||
if (m_pref.general.keepEditedSpriteData())
|
||||
keepEditedSpriteData()->setSelected(true);
|
||||
|
||||
if (m_pref.general.showFullPath())
|
||||
showFullPath()->setSelected(true);
|
||||
|
||||
@ -249,6 +252,10 @@ public:
|
||||
dataRecoveryPeriod()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.dataRecoveryPeriod())));
|
||||
|
||||
keepEditedSpriteDataLifespan()->setSelectedItemIndex(
|
||||
keepEditedSpriteDataLifespan()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.keepEditedSpriteDataLifespan())));
|
||||
|
||||
if (m_pref.editor.zoomFromCenterWithWheel())
|
||||
zoomFromCenterWithWheel()->setSelected(true);
|
||||
|
||||
@ -479,6 +486,15 @@ public:
|
||||
warnings += "<<- " + Strings::alerts_restart_by_preferences_save_recovery_data_period();
|
||||
}
|
||||
|
||||
int newLifespan = base::convert_to<int>(keepEditedSpriteDataLifespan()->getValue());
|
||||
if (keepEditedSpriteData()->isSelected() != m_pref.general.keepEditedSpriteData() ||
|
||||
newLifespan != m_pref.general.dataRecoveryPeriod()) {
|
||||
m_pref.general.keepEditedSpriteData(keepEditedSpriteData()->isSelected());
|
||||
m_pref.general.keepEditedSpriteDataLifespan(newLifespan);
|
||||
|
||||
warnings += "<<- " + Strings::alerts_restart_by_preferences_keep_edited_sprite_data_lifespan();
|
||||
}
|
||||
|
||||
m_pref.editor.zoomFromCenterWithWheel(zoomFromCenterWithWheel()->isSelected());
|
||||
m_pref.editor.zoomFromCenterWithKeys(zoomFromCenterWithKeys()->isSelected());
|
||||
m_pref.editor.showScrollbars(showScrollbars()->isSelected());
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/context.h"
|
||||
#include "app/crash/recovery_config.h"
|
||||
#include "app/crash/session.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_access.h"
|
||||
@ -56,11 +57,13 @@ public:
|
||||
|
||||
}
|
||||
|
||||
BackupObserver::BackupObserver(Session* session, Context* ctx)
|
||||
: m_session(session)
|
||||
BackupObserver::BackupObserver(RecoveryConfig* config,
|
||||
Session* session,
|
||||
Context* ctx)
|
||||
: m_config(config)
|
||||
, m_session(session)
|
||||
, m_ctx(ctx)
|
||||
, m_done(false)
|
||||
, m_period(Preferences::instance().general.dataRecoveryPeriod())
|
||||
, m_thread(base::Bind<void>(&BackupObserver::backgroundThread, this))
|
||||
{
|
||||
m_ctx->add_observer(this);
|
||||
@ -86,19 +89,20 @@ void BackupObserver::onAddDocument(Doc* document)
|
||||
m_documents.push_back(document);
|
||||
}
|
||||
|
||||
void BackupObserver::onRemoveDocument(Doc* document)
|
||||
void BackupObserver::onRemoveDocument(Doc* doc)
|
||||
{
|
||||
TRACE("RECO: Remove document %p\n", document);
|
||||
TRACE("RECO: Remove document %p\n", doc);
|
||||
{
|
||||
base::scoped_lock hold(m_mutex);
|
||||
base::remove_from_container(m_documents, document);
|
||||
base::remove_from_container(m_documents, doc);
|
||||
}
|
||||
m_session->removeDocument(document);
|
||||
// TODO save backup data of the closed document in a background thread
|
||||
m_session->removeDocument(doc);
|
||||
}
|
||||
|
||||
void BackupObserver::backgroundThread()
|
||||
{
|
||||
int normalPeriod = int(60.0*m_period);
|
||||
int normalPeriod = int(60.0*m_config->dataRecoveryPeriod);
|
||||
int lockedPeriod = 5;
|
||||
#ifdef TEST_BACKUPS_WITH_A_SHORT_PERIOD
|
||||
normalPeriod = 5;
|
||||
|
@ -21,13 +21,16 @@ namespace app {
|
||||
class Context;
|
||||
class Doc;
|
||||
namespace crash {
|
||||
struct RecoveryConfig;
|
||||
class Session;
|
||||
|
||||
class BackupObserver : public ContextObserver
|
||||
, public DocsObserver
|
||||
, public DocObserver {
|
||||
public:
|
||||
BackupObserver(Session* session, Context* ctx);
|
||||
BackupObserver(RecoveryConfig* config,
|
||||
Session* session,
|
||||
Context* ctx);
|
||||
~BackupObserver();
|
||||
|
||||
void stop();
|
||||
@ -38,12 +41,12 @@ namespace crash {
|
||||
private:
|
||||
void backgroundThread();
|
||||
|
||||
RecoveryConfig* m_config;
|
||||
Session* m_session;
|
||||
base::mutex m_mutex;
|
||||
Context* m_ctx;
|
||||
std::vector<Doc*> m_documents;
|
||||
bool m_done;
|
||||
double m_period;
|
||||
base::thread m_thread;
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,54 +13,34 @@
|
||||
|
||||
#include "app/crash/backup_observer.h"
|
||||
#include "app/crash/session.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/resource_finder.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/time.h"
|
||||
#include "ui/system.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace app {
|
||||
namespace crash {
|
||||
|
||||
// Flag used to avoid calling SessionsListIsReady() signal after
|
||||
// DataRecovery() instance is deleted.
|
||||
static bool g_stillAliveFlag = false;
|
||||
|
||||
DataRecovery::DataRecovery(Context* ctx)
|
||||
: m_inProgress(nullptr)
|
||||
, m_backup(nullptr)
|
||||
, m_searching(false)
|
||||
{
|
||||
auto& pref = Preferences::instance();
|
||||
m_config.dataRecoveryPeriod = pref.general.dataRecoveryPeriod();
|
||||
m_config.keepEditedSpriteData = pref.general.keepEditedSpriteData();
|
||||
m_config.keepEditedSpriteDataLifespan = pref.general.keepEditedSpriteDataLifespan();
|
||||
|
||||
ResourceFinder rf;
|
||||
rf.includeUserDir(base::join_path("sessions", ".").c_str());
|
||||
std::string sessionsDir = rf.getFirstOrCreateDefault();
|
||||
|
||||
// Existent sessions
|
||||
TRACE("RECO: Listing sessions from '%s'\n", sessionsDir.c_str());
|
||||
for (auto& itemname : base::list_files(sessionsDir)) {
|
||||
std::string itempath = base::join_path(sessionsDir, itemname);
|
||||
if (base::is_directory(itempath)) {
|
||||
TRACE("RECO: Session '%s' ", itempath.c_str());
|
||||
|
||||
SessionPtr session(new Session(itempath));
|
||||
if (!session->isRunning()) {
|
||||
if (session->version() != VERSION) {
|
||||
TRACE("cannot be loaded (incompatible version)\n");
|
||||
}
|
||||
else if (!session->isEmpty()) {
|
||||
TRACE("to be loaded\n");
|
||||
m_sessions.push_back(session);
|
||||
}
|
||||
else {
|
||||
TRACE("to be deleted\n");
|
||||
session->removeFromDisk();
|
||||
}
|
||||
}
|
||||
else
|
||||
TRACE("is running\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Sort sessions from the most recent one to the oldest one
|
||||
std::sort(m_sessions.begin(), m_sessions.end(),
|
||||
[](const SessionPtr& a, const SessionPtr& b) {
|
||||
return a->name() > b->name();
|
||||
});
|
||||
m_sessionsDir = rf.getFirstOrCreateDefault();
|
||||
|
||||
// Create a new session
|
||||
base::pid pid = base::get_current_process_id();
|
||||
@ -73,7 +54,7 @@ DataRecovery::DataRecovery(Context* ctx)
|
||||
time.year, time.month, time.day,
|
||||
time.hour, time.minute, time.second, pid);
|
||||
|
||||
newSessionDir = base::join_path(sessionsDir, buf);
|
||||
newSessionDir = base::join_path(m_sessionsDir, buf);
|
||||
|
||||
if (!base::is_directory(newSessionDir))
|
||||
base::make_directory(newSessionDir);
|
||||
@ -83,23 +64,120 @@ DataRecovery::DataRecovery(Context* ctx)
|
||||
}
|
||||
} while (newSessionDir.empty());
|
||||
|
||||
m_inProgress.reset(new Session(newSessionDir));
|
||||
m_inProgress.reset(new Session(&m_config, newSessionDir));
|
||||
m_inProgress->create(pid);
|
||||
TRACE("RECO: Session in progress '%s'\n", newSessionDir.c_str());
|
||||
|
||||
m_backup = new BackupObserver(m_inProgress.get(), ctx);
|
||||
m_backup = new BackupObserver(&m_config, m_inProgress.get(), ctx);
|
||||
|
||||
g_stillAliveFlag = true;
|
||||
}
|
||||
|
||||
DataRecovery::~DataRecovery()
|
||||
{
|
||||
g_stillAliveFlag = false;
|
||||
m_thread.join();
|
||||
|
||||
m_backup->stop();
|
||||
delete m_backup;
|
||||
|
||||
// We just close the session on progress. The session is not
|
||||
// deleted just in case that the user want to recover some files
|
||||
// from this session in the future.
|
||||
if (m_inProgress)
|
||||
m_inProgress->removeFromDisk();
|
||||
m_inProgress->close();
|
||||
|
||||
m_inProgress.reset();
|
||||
}
|
||||
|
||||
void DataRecovery::launchSearch()
|
||||
{
|
||||
if (m_searching)
|
||||
return;
|
||||
|
||||
// Search current sessions in a background thread
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
|
||||
ASSERT(!m_searching);
|
||||
m_searching = true;
|
||||
|
||||
m_thread = std::thread(
|
||||
[this]{
|
||||
searchForSessions();
|
||||
m_searching = false;
|
||||
});
|
||||
}
|
||||
|
||||
bool DataRecovery::hasRecoverySessions() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_sessionsMutex);
|
||||
|
||||
for (const auto& session : m_sessions)
|
||||
if (session->isCrashedSession())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
DataRecovery::Sessions DataRecovery::sessions()
|
||||
{
|
||||
Sessions copy;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_sessionsMutex);
|
||||
copy = m_sessions;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
void DataRecovery::searchForSessions()
|
||||
{
|
||||
Sessions sessions;
|
||||
|
||||
// Existent sessions
|
||||
TRACE("RECO: Listing sessions from '%s'\n", m_sessionsDir.c_str());
|
||||
for (auto& itemname : base::list_files(m_sessionsDir)) {
|
||||
std::string itempath = base::join_path(m_sessionsDir, itemname);
|
||||
if (base::is_directory(itempath)) {
|
||||
TRACE("RECO: Session '%s'\n", itempath.c_str());
|
||||
|
||||
SessionPtr session(new Session(&m_config, itempath));
|
||||
if (!session->isRunning()) {
|
||||
if ((session->isEmpty()) ||
|
||||
(!session->isCrashedSession() && session->isOldSession())) {
|
||||
TRACE("RECO: - to be deleted (%s)\n",
|
||||
session->isEmpty() ? "is empty":
|
||||
(session->isOldSession() ? "is old":
|
||||
"unknown reason"));
|
||||
session->removeFromDisk();
|
||||
}
|
||||
else {
|
||||
TRACE("RECO: - to be loaded\n");
|
||||
sessions.push_back(session);
|
||||
}
|
||||
}
|
||||
else
|
||||
TRACE("is running\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Sort sessions from the most recent one to the oldest one
|
||||
std::sort(sessions.begin(), sessions.end(),
|
||||
[](const SessionPtr& a, const SessionPtr& b) {
|
||||
return a->name() > b->name();
|
||||
});
|
||||
|
||||
// Assign m_sessions=sessions
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_sessionsMutex);
|
||||
std::swap(m_sessions, sessions);
|
||||
}
|
||||
|
||||
ui::execute_from_ui_thread(
|
||||
[this]{
|
||||
if (g_stillAliveFlag)
|
||||
SessionsListIsReady();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace crash
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -8,9 +9,13 @@
|
||||
#define APP_CRASH_DATA_RECOVERY_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/crash/recovery_config.h"
|
||||
#include "app/crash/session.h"
|
||||
#include "base/disable_copying.h"
|
||||
#include "obs/signal.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace app {
|
||||
@ -25,15 +30,35 @@ namespace crash {
|
||||
DataRecovery(Context* context);
|
||||
~DataRecovery();
|
||||
|
||||
// Launches the thread to search for sessions.
|
||||
void launchSearch();
|
||||
|
||||
// Returns true if there is at least one sessions with sprites to
|
||||
// recover (i.e. a crashed session were changes weren't saved)
|
||||
bool hasRecoverySessions() const;
|
||||
|
||||
Session* activeSession() { return m_inProgress.get(); }
|
||||
|
||||
// Returns the list of sessions that can be recovered.
|
||||
const Sessions& sessions() { return m_sessions; }
|
||||
// Returns a copy of the list of sessions that can be recovered.
|
||||
Sessions sessions();
|
||||
|
||||
// Triggered in the UI-thread from the m_thread using an
|
||||
// ui::execute_from_ui_thread() when the list of sessions is ready
|
||||
// to be used.
|
||||
obs::signal<void()> SessionsListIsReady;
|
||||
|
||||
private:
|
||||
// Executed from m_thread to search for the list of sessions.
|
||||
void searchForSessions();
|
||||
|
||||
std::string m_sessionsDir;
|
||||
mutable std::mutex m_sessionsMutex;
|
||||
std::thread m_thread;
|
||||
RecoveryConfig m_config;
|
||||
Sessions m_sessions;
|
||||
SessionPtr m_inProgress;
|
||||
BackupObserver* m_backup;
|
||||
bool m_searching;
|
||||
|
||||
DISABLE_COPYING(DataRecovery);
|
||||
};
|
||||
|
25
src/app/crash/recovery_config.h
Normal file
25
src/app/crash/recovery_config.h
Normal file
@ -0,0 +1,25 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_CRASH_RECOVERY_CONFIG_H_INCLUDED
|
||||
#define APP_CRASH_RECOVERY_CONFIG_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
namespace app {
|
||||
namespace crash {
|
||||
|
||||
// Structure to store the configuration from Preferences instance to
|
||||
// avoid accessing to Preferences from a non-UI thread.
|
||||
struct RecoveryConfig {
|
||||
double dataRecoveryPeriod;
|
||||
bool keepEditedSpriteData;
|
||||
int keepEditedSpriteDataLifespan;
|
||||
};
|
||||
|
||||
} // namespace crash
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -13,6 +14,7 @@
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/crash/read_document.h"
|
||||
#include "app/crash/recovery_config.h"
|
||||
#include "app/crash/write_document.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_access.h"
|
||||
@ -25,11 +27,16 @@
|
||||
#include "base/process.h"
|
||||
#include "base/split_string.h"
|
||||
#include "base/string.h"
|
||||
#include "base/time.h"
|
||||
#include "doc/cancel_io.h"
|
||||
|
||||
namespace app {
|
||||
namespace crash {
|
||||
|
||||
static const char* kPidFilename = "pid"; // Process ID running the session (or non-existent if the PID was closed correctly)
|
||||
static const char* kVerFilename = "ver"; // File that indicates the Aseprite version used in the session
|
||||
static const char* kOpenFilename = "open"; // File that indicates if the document is/was open in the session (or non-existent if the document was closed correctly)
|
||||
|
||||
Session::Backup::Backup(const std::string& dir)
|
||||
: m_dir(dir)
|
||||
{
|
||||
@ -48,9 +55,11 @@ Session::Backup::Backup(const std::string& dir)
|
||||
m_desc = &buf[0];
|
||||
}
|
||||
|
||||
Session::Session(const std::string& path)
|
||||
Session::Session(RecoveryConfig* config,
|
||||
const std::string& path)
|
||||
: m_pid(0)
|
||||
, m_path(path)
|
||||
, m_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
@ -108,7 +117,31 @@ const Session::Backups& Session::backups()
|
||||
bool Session::isRunning()
|
||||
{
|
||||
loadPid();
|
||||
return base::is_process_running(m_pid);
|
||||
if (m_pid)
|
||||
return base::is_process_running(m_pid);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Session::isCrashedSession()
|
||||
{
|
||||
loadPid();
|
||||
return (m_pid != 0);
|
||||
}
|
||||
|
||||
bool Session::isOldSession()
|
||||
{
|
||||
if (!m_config->keepEditedSpriteData)
|
||||
return true;
|
||||
|
||||
std::string verfile = verFilename();
|
||||
if (!base::is_file(verfile))
|
||||
return true;
|
||||
|
||||
int lifespan = m_config->keepEditedSpriteDataLifespan;
|
||||
base::Time sessionTime = base::get_modification_time(verfile);
|
||||
|
||||
return (sessionTime.addDays(lifespan) < base::current_time());
|
||||
}
|
||||
|
||||
bool Session::isEmpty()
|
||||
@ -131,9 +164,32 @@ void Session::create(base::pid pid)
|
||||
verf << VERSION;
|
||||
}
|
||||
|
||||
void Session::close()
|
||||
{
|
||||
try {
|
||||
// Just remove the PID file to indicate that this session was
|
||||
// correctly closed
|
||||
if (base::is_file(pidFilename()))
|
||||
base::delete_file(pidFilename());
|
||||
|
||||
// If we don't have to keep the sprite data, just remove it from
|
||||
// the disk.
|
||||
if (!m_config->keepEditedSpriteData)
|
||||
removeFromDisk();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
// TODO Log this error
|
||||
}
|
||||
}
|
||||
|
||||
void Session::removeFromDisk()
|
||||
{
|
||||
try {
|
||||
// Remove all backups from disk
|
||||
Backups baks = backups();
|
||||
for (Backup* bak : baks)
|
||||
deleteBackup(bak);
|
||||
|
||||
if (base::is_file(pidFilename()))
|
||||
base::delete_file(pidFilename());
|
||||
|
||||
@ -173,9 +229,20 @@ bool Session::saveDocumentChanges(Doc* doc)
|
||||
base::convert_to<std::string>(doc->id()));
|
||||
TRACE("RECO: Saving document '%s'...\n", dir.c_str());
|
||||
|
||||
// Create directory for document
|
||||
if (!base::is_directory(dir))
|
||||
base::make_directory(dir);
|
||||
|
||||
// Create "open" file to indicate that the document is open in this session
|
||||
{
|
||||
std::string openfile = base::join_path(dir, kOpenFilename);
|
||||
if (!base::is_file(openfile)) {
|
||||
std::ofstream of(FSTREAM_PATH(openfile));
|
||||
if (of)
|
||||
of << "open";
|
||||
}
|
||||
}
|
||||
|
||||
// Save document information
|
||||
return write_document(dir, doc, &reader);
|
||||
}
|
||||
@ -185,11 +252,7 @@ void Session::removeDocument(Doc* doc)
|
||||
try {
|
||||
delete_document_internals(doc);
|
||||
|
||||
// Delete document backup directory
|
||||
std::string dir = base::join_path(m_path,
|
||||
base::convert_to<std::string>(doc->id()));
|
||||
if (base::is_directory(dir))
|
||||
deleteDirectory(dir);
|
||||
markDocumentAsCorrectlyClosed(doc);
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
// TODO Log this error
|
||||
@ -245,7 +308,8 @@ void Session::restoreRawImages(Backup* backup, RawImagesAs as)
|
||||
try {
|
||||
Doc* doc = read_document_with_raw_images(backup->dir(), as);
|
||||
if (doc) {
|
||||
fixFilename(doc);
|
||||
if (isCrashedSession())
|
||||
fixFilename(doc);
|
||||
UIContext::instance()->documents().add(doc);
|
||||
}
|
||||
}
|
||||
@ -285,12 +349,28 @@ void Session::loadPid()
|
||||
|
||||
std::string Session::pidFilename() const
|
||||
{
|
||||
return base::join_path(m_path, "pid");
|
||||
return base::join_path(m_path, kPidFilename);
|
||||
}
|
||||
|
||||
std::string Session::verFilename() const
|
||||
{
|
||||
return base::join_path(m_path, "ver");
|
||||
return base::join_path(m_path, kVerFilename);
|
||||
}
|
||||
|
||||
void Session::markDocumentAsCorrectlyClosed(app::Doc* doc)
|
||||
{
|
||||
std::string dir = base::join_path(
|
||||
m_path, base::convert_to<std::string>(doc->id()));
|
||||
|
||||
ASSERT(!dir.empty());
|
||||
if (dir.empty() || !base::is_directory(dir))
|
||||
return;
|
||||
|
||||
std::string openFn = base::join_path(dir, kOpenFilename);
|
||||
if (base::is_file(openFn)) {
|
||||
TRACE("RECO: Document was closed correctly, deleting file '%s'\n", openFn.c_str());
|
||||
base::delete_file(openFn);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::deleteDirectory(const std::string& dir)
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -21,6 +22,7 @@
|
||||
namespace app {
|
||||
class Doc;
|
||||
namespace crash {
|
||||
struct RecoveryConfig;
|
||||
|
||||
// A class to record/restore session information.
|
||||
class Session {
|
||||
@ -36,7 +38,8 @@ namespace crash {
|
||||
};
|
||||
typedef std::vector<Backup*> Backups;
|
||||
|
||||
Session(const std::string& path);
|
||||
Session(RecoveryConfig* config,
|
||||
const std::string& path);
|
||||
~Session();
|
||||
|
||||
std::string name() const;
|
||||
@ -44,9 +47,12 @@ namespace crash {
|
||||
const Backups& backups();
|
||||
|
||||
bool isRunning();
|
||||
bool isCrashedSession();
|
||||
bool isOldSession();
|
||||
bool isEmpty();
|
||||
|
||||
void create(base::pid pid);
|
||||
void close();
|
||||
void removeFromDisk();
|
||||
|
||||
bool saveDocumentChanges(Doc* doc);
|
||||
@ -63,6 +69,7 @@ namespace crash {
|
||||
void loadPid();
|
||||
std::string pidFilename() const;
|
||||
std::string verFilename() const;
|
||||
void markDocumentAsCorrectlyClosed(Doc* doc);
|
||||
void deleteDirectory(const std::string& dir);
|
||||
void fixFilename(Doc* doc);
|
||||
|
||||
@ -70,6 +77,7 @@ namespace crash {
|
||||
std::string m_path;
|
||||
std::string m_version;
|
||||
Backups m_backups;
|
||||
RecoveryConfig* m_config;
|
||||
|
||||
DISABLE_COPYING(Session);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,6 +25,7 @@
|
||||
#include "ui/alert.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/entry.h"
|
||||
#include "ui/label.h"
|
||||
#include "ui/listitem.h"
|
||||
#include "ui/message.h"
|
||||
#include "ui/resize_event.h"
|
||||
@ -69,8 +71,9 @@ private:
|
||||
|
||||
DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
|
||||
: m_dataRecovery(dataRecovery)
|
||||
, m_openButton("Recover Sprite")
|
||||
, m_deleteButton("Delete")
|
||||
, m_openButton(Strings::recover_files_recover_sprite().c_str())
|
||||
, m_deleteButton(Strings::recover_files_delete())
|
||||
, m_refreshButton(Strings::recover_files_refresh())
|
||||
{
|
||||
m_listBox.setMultiselect(true);
|
||||
m_view.setExpansive(true);
|
||||
@ -79,6 +82,7 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
|
||||
HBox* hbox = new HBox;
|
||||
hbox->addChild(&m_openButton);
|
||||
hbox->addChild(&m_deleteButton);
|
||||
hbox->addChild(&m_refreshButton);
|
||||
addChild(hbox);
|
||||
addChild(&m_view);
|
||||
|
||||
@ -103,23 +107,66 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
|
||||
m_openButton.Click.connect(base::Bind(&DataRecoveryView::onOpen, this));
|
||||
m_openButton.DropDownClick.connect(base::Bind<void>(&DataRecoveryView::onOpenMenu, this));
|
||||
m_deleteButton.Click.connect(base::Bind(&DataRecoveryView::onDelete, this));
|
||||
m_refreshButton.Click.connect(base::Bind(&DataRecoveryView::onRefresh, this));
|
||||
m_listBox.Change.connect(base::Bind(&DataRecoveryView::onChangeSelection, this));
|
||||
m_listBox.DoubleClickItem.connect(base::Bind(&DataRecoveryView::onOpen, this));
|
||||
}
|
||||
|
||||
void DataRecoveryView::fillList()
|
||||
void DataRecoveryView::refreshListNotification()
|
||||
{
|
||||
fillList();
|
||||
layout();
|
||||
}
|
||||
|
||||
void DataRecoveryView::clearList()
|
||||
{
|
||||
WidgetsList children = m_listBox.children();
|
||||
for (auto child : children) {
|
||||
m_listBox.removeChild(child);
|
||||
child->deferDelete();
|
||||
}
|
||||
}
|
||||
|
||||
void DataRecoveryView::fillList()
|
||||
{
|
||||
clearList();
|
||||
fillListWith(true);
|
||||
fillListWith(false);
|
||||
}
|
||||
|
||||
void DataRecoveryView::fillListWith(const bool crashes)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
for (auto& session : m_dataRecovery->sessions()) {
|
||||
if (session->isEmpty())
|
||||
if ((session->isEmpty()) ||
|
||||
(crashes && !session->isCrashedSession()) ||
|
||||
(!crashes && session->isCrashedSession()))
|
||||
continue;
|
||||
|
||||
auto sep = new SeparatorInView(session->name(), HORIZONTAL);
|
||||
if (first) {
|
||||
first = false;
|
||||
|
||||
// Separator for "crash sessions" vs "old sessions"
|
||||
auto sep = new Separator(
|
||||
(crashes ? Strings::recover_files_crash_sessions():
|
||||
Strings::recover_files_old_sessions()), HORIZONTAL);
|
||||
sep->InitTheme.connect(
|
||||
[sep]{
|
||||
sep->setStyle(skin::SkinTheme::instance()->styles.separatorInViewReverse());
|
||||
sep->setBorder(sep->border() + gfx::Border(0, 8, 0, 8)*guiscale());
|
||||
});
|
||||
sep->initTheme();
|
||||
m_listBox.addChild(sep);
|
||||
}
|
||||
|
||||
std::string title = session->name();
|
||||
if (session->version() != VERSION)
|
||||
title =
|
||||
fmt::format(Strings::recover_files_incompatible(),
|
||||
title, session->version());
|
||||
|
||||
auto sep = new SeparatorInView(title, HORIZONTAL);
|
||||
sep->InitTheme.connect(
|
||||
[sep]{
|
||||
sep->setBorder(sep->border() + gfx::Border(0, 8, 0, 8)*guiscale());
|
||||
@ -133,13 +180,14 @@ void DataRecoveryView::fillList()
|
||||
}
|
||||
}
|
||||
|
||||
if (m_listBox.getItemsCount() == 0)
|
||||
// If there are no crash items, we call Empty() signal
|
||||
if (crashes && first)
|
||||
Empty();
|
||||
}
|
||||
|
||||
std::string DataRecoveryView::getTabText()
|
||||
{
|
||||
return "Data Recovery";
|
||||
return Strings::recover_files_title();
|
||||
}
|
||||
|
||||
TabIcon DataRecoveryView::getTabIcon()
|
||||
@ -197,8 +245,8 @@ void DataRecoveryView::onOpenMenu()
|
||||
gfx::Rect bounds = m_openButton.bounds();
|
||||
|
||||
Menu menu;
|
||||
MenuItem rawFrames("Raw Images as Frames");
|
||||
MenuItem rawLayers("Raw Images as Layers");
|
||||
MenuItem rawFrames(Strings::recover_files_raw_images_as_frames());
|
||||
MenuItem rawLayers(Strings::recover_files_raw_images_as_layers());
|
||||
menu.addChild(&rawFrames);
|
||||
menu.addChild(&rawLayers);
|
||||
|
||||
@ -210,7 +258,7 @@ void DataRecoveryView::onOpenMenu()
|
||||
|
||||
void DataRecoveryView::onDelete()
|
||||
{
|
||||
std::vector<Item*> items;
|
||||
std::vector<Item*> items; // Items to delete.
|
||||
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (!widget->isSelected())
|
||||
@ -238,10 +286,24 @@ void DataRecoveryView::onDelete()
|
||||
}
|
||||
onChangeSelection();
|
||||
|
||||
// Check if there is no more crash sessions
|
||||
if (!thereAreCrashSessions())
|
||||
Empty();
|
||||
|
||||
m_listBox.layout();
|
||||
m_view.updateView();
|
||||
}
|
||||
|
||||
void DataRecoveryView::onRefresh()
|
||||
{
|
||||
clearList();
|
||||
onChangeSelection();
|
||||
m_listBox.addChild(new ListItem("Loading..."));
|
||||
layout();
|
||||
|
||||
m_dataRecovery->launchSearch();
|
||||
}
|
||||
|
||||
void DataRecoveryView::onChangeSelection()
|
||||
{
|
||||
int count = 0;
|
||||
@ -256,10 +318,27 @@ void DataRecoveryView::onChangeSelection()
|
||||
|
||||
m_deleteButton.setEnabled(count > 0);
|
||||
m_openButton.setEnabled(count > 0);
|
||||
if (count < 2)
|
||||
m_openButton.mainButton()->setText("Recover Sprite");
|
||||
else
|
||||
m_openButton.mainButton()->setTextf("Recover %d Sprites", count);
|
||||
if (count < 2) {
|
||||
m_openButton.mainButton()->setText(
|
||||
fmt::format(Strings::recover_files_recover_sprite(), count));
|
||||
}
|
||||
else {
|
||||
m_openButton.mainButton()->setText(
|
||||
fmt::format(Strings::recover_files_recover_n_sprites(), count));
|
||||
}
|
||||
}
|
||||
|
||||
bool DataRecoveryView::thereAreCrashSessions() const
|
||||
{
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (auto item = dynamic_cast<const Item*>(widget)) {
|
||||
if (item &&
|
||||
item->session() &&
|
||||
item->session()->isCrashedSession())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -28,6 +29,10 @@ namespace app {
|
||||
public:
|
||||
DataRecoveryView(crash::DataRecovery* dataRecovery);
|
||||
|
||||
// Called after the "Refresh" button is pressed (onRefresh) and
|
||||
// the crash::DataRecovery::SessionsListIsReady signal is received.
|
||||
void refreshListNotification();
|
||||
|
||||
// TabView implementation
|
||||
std::string getTabText() override;
|
||||
TabIcon getTabIcon() override;
|
||||
@ -38,24 +43,29 @@ namespace app {
|
||||
bool onCloseView(Workspace* workspace, bool quitting) override;
|
||||
void onTabPopup(Workspace* workspace) override;
|
||||
|
||||
// Triggered when the list is empty (because the user deleted all
|
||||
// sessions).
|
||||
// Triggered when the list is of crashed sessions is empty (or
|
||||
// because the user deleted all sessions that crashed).
|
||||
obs::signal<void()> Empty;
|
||||
|
||||
private:
|
||||
void clearList();
|
||||
void fillList();
|
||||
void fillListWith(const bool crashes);
|
||||
|
||||
void onOpen();
|
||||
void onOpenRaw(crash::RawImagesAs as);
|
||||
void onOpenMenu();
|
||||
void onDelete();
|
||||
void onRefresh();
|
||||
void onChangeSelection();
|
||||
bool thereAreCrashSessions() const;
|
||||
|
||||
crash::DataRecovery* m_dataRecovery;
|
||||
ui::View m_view;
|
||||
ui::ListBox m_listBox;
|
||||
DropDownButton m_openButton;
|
||||
ui::Button m_deleteButton;
|
||||
ui::Button m_refreshButton;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "app/app_menus.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/crash/data_recovery.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/ui/data_recovery_view.h"
|
||||
#include "app/ui/main_window.h"
|
||||
@ -47,7 +48,7 @@ HomeView::HomeView()
|
||||
#ifdef ENABLE_NEWS
|
||||
, m_news(new NewsListBox)
|
||||
#endif
|
||||
, m_dataRecovery(nullptr)
|
||||
, m_dataRecovery(App::instance()->dataRecovery())
|
||||
, m_dataRecoveryView(nullptr)
|
||||
{
|
||||
newFile()->Click.connect(base::Bind(&HomeView::onNewFile, this));
|
||||
@ -61,7 +62,6 @@ HomeView::HomeView()
|
||||
#endif
|
||||
|
||||
checkUpdate()->setVisible(false);
|
||||
recoverSpritesPlaceholder()->setVisible(false);
|
||||
|
||||
InitTheme.connect(
|
||||
[this]{
|
||||
@ -82,11 +82,18 @@ HomeView::~HomeView()
|
||||
#endif
|
||||
}
|
||||
|
||||
void HomeView::showDataRecovery(crash::DataRecovery* dataRecovery)
|
||||
void HomeView::dataRecoverySessionsAreReady()
|
||||
{
|
||||
#ifdef ENABLE_DATA_RECOVERY
|
||||
m_dataRecovery = dataRecovery;
|
||||
recoverSpritesPlaceholder()->setVisible(true);
|
||||
if (App::instance()->dataRecovery()->hasRecoverySessions()) {
|
||||
// We highlight the "Recover Files" options because we came from a crash
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
recoverSprites()->setStyle(theme->styles.workspaceUpdateLink());
|
||||
layout();
|
||||
}
|
||||
if (m_dataRecoveryView) {
|
||||
m_dataRecoveryView->refreshListNotification();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -195,11 +202,13 @@ void HomeView::onRecoverSprites()
|
||||
if (!m_dataRecoveryView) {
|
||||
m_dataRecoveryView = new DataRecoveryView(m_dataRecovery);
|
||||
|
||||
// Hide the "Recover Lost Sprites" button when the
|
||||
// DataRecoveryView is empty.
|
||||
// Restore the "Recover Files" link style when the
|
||||
// DataRecoveryView is empty (so there is no more warning icon on
|
||||
// it).
|
||||
m_dataRecoveryView->Empty.connect(
|
||||
[this]{
|
||||
recoverSpritesPlaceholder()->setVisible(false);
|
||||
SkinTheme* theme = static_cast<SkinTheme*>(this->theme());
|
||||
recoverSprites()->setStyle(theme->styles.workspaceLink());
|
||||
layout();
|
||||
});
|
||||
}
|
||||
|
@ -42,7 +42,9 @@ namespace app {
|
||||
HomeView();
|
||||
~HomeView();
|
||||
|
||||
void showDataRecovery(crash::DataRecovery* dataRecovery);
|
||||
// When crash::DataRecovery finish to search for sessions, this
|
||||
// function is called.
|
||||
void dataRecoverySessionsAreReady();
|
||||
|
||||
// TabView implementation
|
||||
std::string getTabText() override;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,6 +14,7 @@
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/crash/data_recovery.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/modules/editors.h"
|
||||
@ -327,9 +328,9 @@ void MainWindow::popTimeline()
|
||||
setTimelineVisibility(true);
|
||||
}
|
||||
|
||||
void MainWindow::showDataRecovery(crash::DataRecovery* dataRecovery)
|
||||
void MainWindow::dataRecoverySessionsAreReady()
|
||||
{
|
||||
getHomeView()->showDataRecovery(dataRecovery);
|
||||
getHomeView()->dataRecoverySessionsAreReady();
|
||||
}
|
||||
|
||||
bool MainWindow::onProcessMessage(ui::Message* msg)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -81,7 +81,9 @@ namespace app {
|
||||
void setTimelineVisibility(bool visible);
|
||||
void popTimeline();
|
||||
|
||||
void showDataRecovery(crash::DataRecovery* dataRecovery);
|
||||
// When crash::DataRecovery finish to search for sessions, this
|
||||
// function is called.
|
||||
void dataRecoverySessionsAreReady();
|
||||
|
||||
// TabsDelegate implementation.
|
||||
bool isTabModified(Tabs* tabs, TabView* tabView) override;
|
||||
|
Loading…
Reference in New Issue
Block a user