diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index dd3d24811..14db47117 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 diff --git a/src/app/app.cpp b/src/app/app.cpp index 26ded1171..75d308118 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -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) diff --git a/src/app/backup.cpp b/src/app/backup.cpp deleted file mode 100644 index 0471b3a38..000000000 --- a/src/app/backup.cpp +++ /dev/null @@ -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 diff --git a/src/app/backup.h b/src/app/backup.h deleted file mode 100644 index 5678457df..000000000 --- a/src/app/backup.h +++ /dev/null @@ -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 - -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 diff --git a/src/app/crash/data_recovery.cpp b/src/app/crash/data_recovery.cpp new file mode 100644 index 000000000..0b549eedb --- /dev/null +++ b/src/app/crash/data_recovery.cpp @@ -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 + +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 diff --git a/src/app/data_recovery.h b/src/app/crash/data_recovery.h similarity index 70% rename from src/app/data_recovery.h rename to src/app/crash/data_recovery.h index 93f3661de..76a8999a6 100644 --- a/src/app/data_recovery.h +++ b/src/app/crash/data_recovery.h @@ -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 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 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 diff --git a/src/app/crash/session.cpp b/src/app/crash/session.cpp new file mode 100644 index 000000000..57e3c9cee --- /dev/null +++ b/src/app/crash/session.cpp @@ -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 + +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 diff --git a/src/app/crash/session.h b/src/app/crash/session.h new file mode 100644 index 000000000..8427f3b3b --- /dev/null +++ b/src/app/crash/session.h @@ -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 +#include + +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 SessionPtr; + +} // namespace crash +} // namespace app + +#endif diff --git a/src/app/data_recovery.cpp b/src/app/data_recovery.cpp deleted file mode 100644 index 8ba50cd30..000000000 --- a/src/app/data_recovery.cpp +++ /dev/null @@ -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 diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 0bd2cb200..133428467 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -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 diff --git a/src/base/fs.h b/src/base/fs.h index 4b66754ad..944743a3a 100644 --- a/src/base/fs.h +++ b/src/base/fs.h @@ -9,6 +9,7 @@ #pragma once #include +#include namespace base { @@ -35,6 +36,8 @@ namespace base { std::string get_temp_path(); std::string get_user_docs_folder(); -} + std::vector list_files(const std::string& path); + +} // namespace base #endif diff --git a/src/base/fs_win32.h b/src/base/fs_win32.h index 9b55afd29..ef3b8b473 100644 --- a/src/base/fs_win32.h +++ b/src/base/fs_win32.h @@ -129,4 +129,20 @@ std::string get_user_docs_folder() return ""; } +std::vector list_files(const std::string& path) +{ + WIN32_FIND_DATA fd; + std::vector 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; +} + } diff --git a/src/base/process.cpp b/src/base/process.cpp new file mode 100644 index 000000000..c13c21cd0 --- /dev/null +++ b/src/base/process.cpp @@ -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 +#else +#include +#include +#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 diff --git a/src/base/process.h b/src/base/process.h new file mode 100644 index 000000000..95776b552 --- /dev/null +++ b/src/base/process.h @@ -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 diff --git a/src/base/temp_dir.cpp b/src/base/temp_dir.cpp deleted file mode 100644 index ac704de55..000000000 --- a/src/base/temp_dir.cpp +++ /dev/null @@ -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 - -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(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; -} - -} diff --git a/src/base/temp_dir.h b/src/base/temp_dir.h deleted file mode 100644 index 00113ad60..000000000 --- a/src/base/temp_dir.h +++ /dev/null @@ -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 - -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