Change the way crash sessions are stored on disk

Changes:
* Move classes related to data recovery into app/crash/ directory/namespace
* Rename app::Backup to app::crash::Session
* Rename app::DataRecovery to app::crash::DataRecovery
* Add base::list_files()
* Add base::get_current_process_id() and base::is_process_running()
* Remove base::TempDir
* Save one session for each running process
* Remove sessions that are empty when the program starts
This commit is contained in:
David Capello 2015-04-05 18:51:43 -03:00
parent e4667149f4
commit 5c6860e7ac
16 changed files with 364 additions and 253 deletions

View File

@ -60,7 +60,6 @@ add_library(app-lib
app_menus.cpp
app_options.cpp
app_render.cpp
backup.cpp
check_update.cpp
cmd.cpp
cmd/add_cel.cpp
@ -226,7 +225,8 @@ add_library(app-lib
console.cpp
context.cpp
context_flags.cpp
data_recovery.cpp
crash/data_recovery.cpp
crash/session.cpp
document.cpp
document_api.cpp
document_exporter.cpp

View File

@ -19,7 +19,7 @@
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/data_recovery.h"
#include "app/crash/data_recovery.h"
#include "app/document_exporter.h"
#include "app/document_location.h"
#include "app/document_undo.h"
@ -94,7 +94,7 @@ public:
CommandsModule m_commands_modules;
UIContext m_ui_context;
RecentFiles m_recent_files;
app::DataRecovery m_recovery;
app::crash::DataRecovery m_recovery;
scripting::Engine m_scriptingEngine;
Modules(bool console, bool verbose)

View File

@ -1,30 +0,0 @@
// 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/backup.h"
namespace app {
Backup::Backup(const std::string& path)
: m_path(path)
{
}
Backup::~Backup()
{
}
bool Backup::hasDataToRestore()
{
return false;
}
} // namespace app

View File

@ -1,35 +0,0 @@
// 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_BACKUP_H_INCLUDED
#define APP_BACKUP_H_INCLUDED
#pragma once
#include "base/disable_copying.h"
#include <string>
namespace app {
// A class to record/restore backup information.
class Backup {
public:
Backup(const std::string& path);
~Backup();
// Returns true if there are items that can be restored.
bool hasDataToRestore();
private:
DISABLE_COPYING(Backup);
std::string m_path;
};
} // namespace app
#endif

View File

@ -0,0 +1,113 @@
// 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/data_recovery.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"
#include <windows.h>
namespace app {
namespace crash {
DataRecovery::DataRecovery(doc::Context* context)
: m_inProgress(nullptr)
, m_context(context)
{
ResourceFinder rf;
rf.includeUserDir(base::join_path("sessions", ".").c_str());
std::string sessionsDir = rf.getFirstOrCreateDefault();
// Existent sessions
TRACE("DataRecovery: 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("- Session '%s' ", itempath.c_str());
SessionPtr session(new Session(itempath));
if (!session->isRunning()) {
if (!session->isEmpty()) {
TRACE("to be loaded\n", itempath.c_str());
m_sessions.push_back(session);
}
else {
TRACE("to be deleted\n", itempath.c_str());
session->removeFromDisk();
}
}
else
TRACE("is running\n", itempath.c_str());
}
}
// Create a new session
base::pid pid = base::get_current_process_id();
std::string newSessionDir;
do {
base::Time time = base::current_time();
char buf[1024];
sprintf(buf, "%04d%02d%02d-%02d%02d%02d-%d",
time.year, time.month, time.day,
time.hour, time.minute, time.second, pid);
newSessionDir = base::join_path(sessionsDir, buf);
if (!base::is_directory(newSessionDir))
base::make_directory(newSessionDir);
else {
base::this_thread::sleep_for(1);
newSessionDir.clear();
}
} while (newSessionDir.empty());
m_inProgress.reset(new Session(newSessionDir));
m_inProgress->create(pid);
TRACE("DataRecovery: Session in progress '%s'\n", newSessionDir.c_str());
m_context->addObserver(this);
m_context->documents().addObserver(this);
}
DataRecovery::~DataRecovery()
{
m_context->documents().removeObserver(this);
m_context->removeObserver(this);
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

@ -5,50 +5,49 @@
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_DATA_RECOVERY_H_INCLUDED
#define APP_DATA_RECOVERY_H_INCLUDED
#ifndef APP_CRASH_DATA_RECOVERY_H_INCLUDED
#define APP_CRASH_DATA_RECOVERY_H_INCLUDED
#pragma once
#include "app/crash/session.h"
#include "base/disable_copying.h"
#include "base/slot.h"
#include "doc/context_observer.h"
#include "doc/document_observer.h"
#include "doc/documents_observer.h"
namespace base {
class TempDir;
}
#include <vector>
namespace doc {
class Context;
}
namespace app {
class Backup;
namespace crash {
class DataRecovery : public doc::ContextObserver
, public doc::DocumentsObserver
, public doc::DocumentObserver {
public:
typedef std::vector<SessionPtr> Sessions;
DataRecovery(doc::Context* context);
~DataRecovery();
// Returns a backup if there are data to be restored from a
// crash. Or null if the program didn't crash in its previous
// execution.
Backup* getBackup() { return m_backup; }
// Returns the list of sessions that can be recovered.
const Sessions& sessions() { return m_sessions; }
private:
virtual void onAddDocument(doc::Document* document) override;
virtual void onRemoveDocument(doc::Document* document) override;
base::TempDir* m_tempDir;
Backup* m_backup;
Sessions m_sessions;
SessionPtr m_inProgress;
doc::Context* m_context;
DISABLE_COPYING(DataRecovery);
};
} // namespace crash
} // namespace app
#endif

82
src/app/crash/session.cpp Normal file
View File

@ -0,0 +1,82 @@
// 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/session.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/process.h"
#include <windows.h>
namespace app {
namespace crash {
Session::Session(const std::string& path)
: m_path(path)
, m_pid(0)
{
}
Session::~Session()
{
}
bool Session::isRunning()
{
loadPid();
return base::is_process_running(m_pid);
}
bool Session::isEmpty()
{
return !base::is_file(dataFilename());
}
void Session::create(base::pid pid)
{
m_pid = pid;
std::ofstream of(pidFilename());
of << m_pid;
}
void Session::removeFromDisk()
{
base::delete_file(pidFilename());
base::remove_directory(m_path);
}
void Session::loadPid()
{
if (m_pid)
return;
std::string pidfile = pidFilename();
if (base::is_file(pidfile)) {
std::ifstream pf(pidfile);
if (pf)
pf >> m_pid;
}
}
std::string Session::pidFilename() const
{
return base::join_path(m_path, "pid");
}
std::string Session::dataFilename() const
{
return base::join_path(m_path, "data");
}
} // namespace crash
} // namespace app

52
src/app/crash/session.h Normal file
View File

@ -0,0 +1,52 @@
// 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_SESSION_H_INCLUDED
#define APP_CRASH_SESSION_H_INCLUDED
#pragma once
#include "base/disable_copying.h"
#include "base/process.h"
#include "base/shared_ptr.h"
#include <fstream>
#include <string>
namespace app {
namespace crash {
// A class to record/restore session information.
class Session {
public:
Session(const std::string& path);
~Session();
bool isRunning();
bool isEmpty();
void create(base::pid pid);
void removeFromDisk();
private:
void loadPid();
std::string pidFilename() const;
std::string dataFilename() const;
base::pid m_pid;
std::string m_path;
std::fstream m_log;
std::fstream m_pidFile;
DISABLE_COPYING(Session);
};
typedef base::SharedPtr<Session> SessionPtr;
} // namespace crash
} // namespace app
#endif

View File

@ -1,74 +0,0 @@
// 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/data_recovery.h"
#include "app/backup.h"
#include "app/document.h"
#include "app/ini_file.h"
#include "app/ui_context.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/temp_dir.h"
namespace app {
DataRecovery::DataRecovery(doc::Context* context)
: m_tempDir(NULL)
, m_backup(NULL)
, m_context(context)
{
// Check if there is already data to recover
const std::string existent_data_path = get_config_string("DataRecovery", "Path", "");
if (!existent_data_path.empty() &&
base::is_directory(existent_data_path)) {
// Load the backup data.
m_tempDir = new base::TempDir();
m_tempDir->attach(existent_data_path);
m_backup = new Backup(existent_data_path);
}
else {
// Create a new directory to save the backup information.
m_tempDir = new base::TempDir(PACKAGE);
m_backup = new Backup(m_tempDir->path());
set_config_string("DataRecovery", "Path", m_tempDir->path().c_str());
flush_config_file();
}
m_context->addObserver(this);
m_context->documents().addObserver(this);
}
DataRecovery::~DataRecovery()
{
m_context->documents().removeObserver(this);
m_context->removeObserver(this);
delete m_backup;
if (m_tempDir) {
delete m_tempDir;
set_config_string("DataRecovery", "Path", "");
}
}
void DataRecovery::onAddDocument(doc::Document* document)
{
document->addObserver(this);
}
void DataRecovery::onRemoveDocument(doc::Document* document)
{
document->removeObserver(this);
}
} // namespace app

View File

@ -64,6 +64,7 @@ set(BASE_SOURCES
memory_dump.cpp
mutex.cpp
path.cpp
process.cpp
program_options.cpp
replace_string.cpp
serialization.cpp
@ -72,7 +73,6 @@ set(BASE_SOURCES
split_string.cpp
string.cpp
system_console.cpp
temp_dir.cpp
thread.cpp
time.cpp
trim_string.cpp

View File

@ -9,6 +9,7 @@
#pragma once
#include <string>
#include <vector>
namespace base {
@ -35,6 +36,8 @@ namespace base {
std::string get_temp_path();
std::string get_user_docs_folder();
}
std::vector<std::string> list_files(const std::string& path);
} // namespace base
#endif

View File

@ -129,4 +129,20 @@ std::string get_user_docs_folder()
return "";
}
std::vector<std::string> list_files(const std::string& path)
{
WIN32_FIND_DATA fd;
std::vector<std::string> files;
HANDLE handle = FindFirstFile(base::from_utf8(base::join_path(path, "*")).c_str(), &fd);
if (handle) {
do {
std::string filename = base::to_utf8(fd.cFileName);
if (filename != "." && filename != "..")
files.push_back(filename);
} while (FindNextFile(handle, &fd));
FindClose(handle);
}
return files;
}
}

59
src/base/process.cpp Normal file
View File

@ -0,0 +1,59 @@
// Aseprite Base Library
// Copyright (c) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "base/process.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <unistd.h>
#endif
namespace base {
#ifdef _WIN32
pid get_current_process_id()
{
return (pid)GetCurrentProcessId();
}
bool is_process_running(pid pid)
{
bool running = false;
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
if (handle) {
DWORD exitCode = 0;
if (GetExitCodeProcess(handle, &exitCode)) {
running = (exitCode == STILL_ACTIVE);
}
CloseHandle(handle);
}
return running;
}
#else
pid get_current_process_id()
{
return (pid)getpid();
}
bool is_process_running(pid pid)
{
// TODO
}
#endif
} // namespace base

21
src/base/process.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite Base Library
// Copyright (c) 2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef BASE_PROCESS_H_INCLUDED
#define BASE_PROCESS_H_INCLUDED
#pragma once
namespace base {
typedef uint32_t pid;
pid get_current_process_id();
bool is_process_running(pid pid);
} // namespace base
#endif

View File

@ -1,64 +0,0 @@
// Aseprite Base Library
// Copyright (c) 2001-2013 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "base/temp_dir.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
#include <cstdlib>
namespace base {
TempDir::TempDir()
{
}
TempDir::TempDir(const std::string& appName)
{
for (int i=(std::rand()%0xffff); ; ++i) {
m_path = join_path(get_temp_path(),
appName + convert_to<std::string>(i));
if (!is_directory(m_path)) {
make_directory(m_path);
break;
}
}
}
TempDir::~TempDir()
{
remove();
}
void TempDir::remove()
{
if (!m_path.empty()) {
try {
remove_directory(m_path);
}
catch (const std::exception&) {
// Ignore exceptions if the directory cannot be removed.
}
m_path.clear();
}
}
void TempDir::attach(const std::string& path)
{
remove();
ASSERT(is_directory(path));
m_path = path;
}
}

View File

@ -1,31 +0,0 @@
// Aseprite Base Library
// Copyright (c) 2001-2013 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef BASE_TEMP_DIR_H_INCLUDED
#define BASE_TEMP_DIR_H_INCLUDED
#pragma once
#include <string>
namespace base {
class TempDir {
public:
TempDir();
TempDir(const std::string& appName);
~TempDir();
void remove();
void attach(const std::string& path);
const std::string& path() const { return m_path; }
private:
std::string m_path;
};
}
#endif