Save document backups in background session each 5 minutes

This commit is contained in:
David Capello 2015-04-06 12:06:50 -03:00
parent 0a4e710ad7
commit 0bde95650d
7 changed files with 187 additions and 35 deletions

View File

@ -225,6 +225,7 @@ add_library(app-lib
console.cpp
context.cpp
context_flags.cpp
crash/backup_observer.cpp
crash/data_recovery.cpp
crash/session.cpp
document.cpp

View File

@ -0,0 +1,85 @@
// 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.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/crash/backup_observer.h"
#include "app/crash/session.h"
#include "app/document.h"
#include "base/bind.h"
#include "base/remove_from_container.h"
#include "base/scoped_lock.h"
#include "doc/context.h"
namespace app {
namespace crash {
BackupObserver::BackupObserver(Session* session, doc::Context* ctx)
: m_session(session)
, m_ctx(ctx)
, m_done(false)
, m_thread(Bind<void>(&BackupObserver::backgroundThread, this))
{
m_ctx->addObserver(this);
m_ctx->documents().addObserver(this);
}
BackupObserver::~BackupObserver()
{
m_thread.join();
m_ctx->documents().removeObserver(this);
m_ctx->removeObserver(this);
}
void BackupObserver::stop()
{
m_done = true;
}
void BackupObserver::onAddDocument(doc::Document* document)
{
TRACE("DataRecovery: Observe document %p\n", document);
base::scoped_lock hold(m_mutex);
m_documents.push_back(document);
}
void BackupObserver::onRemoveDocument(doc::Document* document)
{
TRACE("DataRecovery:: Remove document %p\n", document);
base::scoped_lock hold(m_mutex);
base::remove_from_container(m_documents, document);
}
void BackupObserver::backgroundThread()
{
int counter = 0;
while (!m_done) {
counter++;
if (counter == 5*60) { // Each 5 minutes
counter = 0;
base::scoped_lock hold(m_mutex);
TRACE("DataRecovery: Start backup process for %d documents\n", m_documents.size());
for (doc::Document* doc : m_documents) {
try {
m_session->saveDocumentChanges(static_cast<app::Document*>(doc));
}
catch (const std::exception&) {
TRACE("DataRecovery: Document '%d' is locked\n", doc->id());
}
}
}
base::this_thread::sleep_for(1.0);
}
}
} // namespace crash
} // namespace app

View File

@ -0,0 +1,54 @@
// 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_BACKUP_OBSERVER_H_INCLUDED
#define APP_CRASH_BACKUP_OBSERVER_H_INCLUDED
#pragma once
#include "base/mutex.h"
#include "base/thread.h"
#include "doc/context_observer.h"
#include "doc/document_observer.h"
#include "doc/documents_observer.h"
#include <vector>
namespace doc {
class Context;
}
namespace app {
namespace crash {
class Session;
class BackupObserver : public doc::ContextObserver
, public doc::DocumentsObserver
, public doc::DocumentObserver {
public:
BackupObserver(Session* session, doc::Context* ctx);
~BackupObserver();
void stop();
void onAddDocument(doc::Document* document) override;
void onRemoveDocument(doc::Document* document) override;
private:
void backgroundThread();
Session* m_session;
base::mutex m_mutex;
doc::Context* m_ctx;
std::vector<doc::Document*> m_documents;
bool m_done;
base::thread m_thread;
};
} // namespace crash
} // namespace app
#endif

View File

@ -11,24 +11,19 @@
#include "app/crash/data_recovery.h"
#include "app/crash/backup_observer.h"
#include "app/crash/session.h"
#include "app/document.h"
#include "app/resource_finder.h"
#include "app/ui_context.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/process.h"
#include "base/string.h"
#include "base/thread.h"
#include "base/time.h"
namespace app {
namespace crash {
DataRecovery::DataRecovery(doc::Context* context)
DataRecovery::DataRecovery(doc::Context* ctx)
: m_inProgress(nullptr)
, m_context(context)
, m_backup(nullptr)
{
ResourceFinder rf;
rf.includeUserDir(base::join_path("sessions", ".").c_str());
@ -83,29 +78,16 @@ DataRecovery::DataRecovery(doc::Context* context)
m_inProgress->create(pid);
TRACE("DataRecovery: Session in progress '%s'\n", newSessionDir.c_str());
m_context->addObserver(this);
m_context->documents().addObserver(this);
m_backup = new BackupObserver(m_inProgress.get(), ctx);
}
DataRecovery::~DataRecovery()
{
m_context->documents().removeObserver(this);
m_context->removeObserver(this);
m_backup->stop();
delete m_backup;
m_inProgress.reset();
}
void DataRecovery::onAddDocument(doc::Document* document)
{
TRACE("DataRecovery: Observe document %p\n", document);
document->addObserver(this);
}
void DataRecovery::onRemoveDocument(doc::Document* document)
{
TRACE("DataRecovery:: Remove document %p\n", document);
document->removeObserver(this);
}
} // namespace crash
} // namespace app

View File

@ -11,9 +11,6 @@
#include "app/crash/session.h"
#include "base/disable_copying.h"
#include "doc/context_observer.h"
#include "doc/document_observer.h"
#include "doc/documents_observer.h"
#include <vector>
@ -23,10 +20,9 @@ namespace doc {
namespace app {
namespace crash {
class BackupObserver;
class DataRecovery : public doc::ContextObserver
, public doc::DocumentsObserver
, public doc::DocumentObserver {
class DataRecovery {
public:
typedef std::vector<SessionPtr> Sessions;
@ -37,12 +33,9 @@ namespace crash {
const Sessions& sessions() { return m_sessions; }
private:
virtual void onAddDocument(doc::Document* document) override;
virtual void onRemoveDocument(doc::Document* document) override;
Sessions m_sessions;
SessionPtr m_inProgress;
doc::Context* m_context;
BackupObserver* m_backup;
DISABLE_COPYING(DataRecovery);
};

View File

@ -11,6 +11,12 @@
#include "app/crash/session.h"
#include "app/context.h"
#include "app/document.h"
#include "app/document_access.h"
#include "app/file/file.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/process.h"
@ -36,7 +42,11 @@ bool Session::isRunning()
bool Session::isEmpty()
{
return !base::is_file(dataFilename());
for (auto& item : base::list_files(m_path)) {
if (base::get_file_extension(item) == "ase")
return false;
}
return true;
}
void Session::create(base::pid pid)
@ -53,6 +63,29 @@ void Session::removeFromDisk()
base::remove_directory(m_path);
}
void Session::saveDocumentChanges(app::Document* doc)
{
DocumentReader reader(doc);
DocumentWriter writer(reader);
app::Context ctx;
std::string fn = base::join_path(m_path,
base::convert_to<std::string>(doc->id()) + ".ase");
TRACE("DataRecovery: Saving document '%s'...\n", fn.c_str());
FileOp* fop = fop_to_save_document(&ctx,
static_cast<app::Document*>(doc), fn.c_str(), "");
if (!fop) {
TRACE("DataRecovery: Cannot create save file operation\n");
return;
}
fop_operate(fop, NULL);
fop_done(fop);
if (fop->has_error())
TRACE("DataRecovery: Error saving changes '%s'\n", fop->error.c_str());
fop_free(fop);
}
void Session::loadPid()
{
if (m_pid)

View File

@ -17,6 +17,8 @@
#include <string>
namespace app {
class Document;
namespace crash {
// A class to record/restore session information.
@ -31,6 +33,8 @@ namespace crash {
void create(base::pid pid);
void removeFromDisk();
void saveDocumentChanges(app::Document* doc);
private:
void loadPid();
std::string pidFilename() const;