Add support to send crash reports from macOS

This commit is contained in:
David Capello 2020-03-06 14:45:13 -03:00
parent e80dfbbdaf
commit 99cb95357a
8 changed files with 131 additions and 37 deletions

View File

@ -606,6 +606,7 @@ crash::DataRecovery* App::dataRecovery() const
#ifdef ENABLE_UI #ifdef ENABLE_UI
void App::showNotification(INotificationDelegate* del) void App::showNotification(INotificationDelegate* del)
{ {
if (m_mainWindow)
m_mainWindow->showNotification(del); m_mainWindow->showNotification(del);
} }
@ -709,18 +710,4 @@ int app_get_color_to_clear_layer(Layer* layer)
return color_utils::color_for_layer(color, layer); return color_utils::color_for_layer(color, layer);
} }
std::string memory_dump_filename()
{
#ifdef _WIN32
static const char* kDefaultCrashName = PACKAGE "-crash-" VERSION ".dmp";
app::ResourceFinder rf;
rf.includeUserDir(kDefaultCrashName);
return rf.getFirstOrCreateDefault();
#else
return "";
#endif
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -117,6 +117,9 @@ namespace app {
script::Engine* scriptEngine() { return m_engine.get(); } script::Engine* scriptEngine() { return m_engine.get(); }
#endif #endif
const std::string& memoryDumpFilename() const { return m_memoryDumpFilename; }
void memoryDumpFilename(const std::string& fn) { m_memoryDumpFilename = fn; }
// App Signals // App Signals
obs::signal<void()> Exit; obs::signal<void()> Exit;
obs::signal<void()> PaletteChange; obs::signal<void()> PaletteChange;
@ -145,13 +148,16 @@ namespace app {
#ifdef ENABLE_SCRIPTING #ifdef ENABLE_SCRIPTING
std::unique_ptr<script::Engine> m_engine; std::unique_ptr<script::Engine> m_engine;
#endif #endif
// Set the memory dump filename to show in the Preferences dialog
// or the "send crash" dialog. It's set by the SendCrash class.
std::string m_memoryDumpFilename;
}; };
void app_refresh_screen(); void app_refresh_screen();
void app_rebuild_documents_tabs(); void app_rebuild_documents_tabs();
PixelFormat app_get_current_pixel_format(); PixelFormat app_get_current_pixel_format();
int app_get_color_to_clear_layer(doc::Layer* layer); int app_get_color_to_clear_layer(doc::Layer* layer);
std::string memory_dump_filename();
} // namespace app } // namespace app

View File

@ -421,11 +421,10 @@ public:
// Links // Links
locateFile()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateConfigFile, this)); locateFile()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateConfigFile, this));
#if _WIN32 if (!App::instance()->memoryDumpFilename().empty())
locateCrashFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateCrashFolder, this)); locateCrashFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onLocateCrashFolder, this));
#else else
locateCrashFolder()->setVisible(false); locateCrashFolder()->setVisible(false);
#endif
// Undo preferences // Undo preferences
limitUndo()->Click.connect(base::Bind<void>(&OptionsWindow::onLimitUndoCheck, this)); limitUndo()->Click.connect(base::Bind<void>(&OptionsWindow::onLimitUndoCheck, this));
@ -1017,7 +1016,8 @@ private:
} }
void onLocateCrashFolder() { void onLocateCrashFolder() {
app::launcher::open_folder(base::get_file_path(app::memory_dump_filename())); app::launcher::open_folder(
base::get_file_path(App::instance()->memoryDumpFilename()));
} }
void onLocateConfigFile() { void onLocateConfigFile() {

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -14,23 +15,92 @@
#include "app/console.h" #include "app/console.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/resource_finder.h" #include "app/resource_finder.h"
#include "app/task.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/fs.h" #include "base/fs.h"
#include "base/launcher.h" #include "base/launcher.h"
#include "ui/alert.h" #include "ui/alert.h"
#include "ui/system.h"
#include "send_crash.xml.h" #include "send_crash.xml.h"
namespace app { namespace app {
// static
std::string SendCrash::DefaultMemoryDumpFilename()
{
#ifdef _WIN32
const char* kDefaultCrashName = PACKAGE "-crash-" VERSION ".dmp";
ResourceFinder rf;
rf.includeUserDir(kDefaultCrashName);
return rf.getFirstOrCreateDefault();
#else
return std::string();
#endif
}
SendCrash::~SendCrash()
{
if (m_task.running()) {
m_task.cancel();
m_task.wait();
}
}
void SendCrash::search() void SendCrash::search()
{ {
#ifdef _WIN32 #ifdef _WIN32
m_dumpFilename = memory_dump_filename(); // On Windows we use one mini-dump to report bugs, then we can open
// this .dmp file locally along with the .exe + .pdb to check the
// stack trace and detect the cause of the bug.
if (base::is_file(m_dumpFilename)) { m_dumpFilename = SendCrash::DefaultMemoryDumpFilename();
App::instance()->showNotification(this); if (!m_dumpFilename.empty() &&
base::is_file(m_dumpFilename)) {
auto app = App::instance();
app->memoryDumpFilename(fn);
app->showNotification(this);
} }
#elif defined(__APPLE__)
// On macOS we can show the possibility to send the latest crash
// report from ~/Library/Logs/DiagnosticReports which is the
// location where crash reports (.crash files) are located.
m_task.run(
[this](base::task_token&){
ResourceFinder rf;
rf.includeHomeDir("Library/Logs/DiagnosticReports");
std::string dir = rf.defaultFilename();
if (base::is_directory(dir)) {
std::vector<std::string> candidates;
int n = std::strlen(PACKAGE);
for (const auto& fn : base::list_files(dir)) {
// Cancel everything
if (m_task.canceled())
return;
if (base::utf8_icmp(PACKAGE, fn, n) == 0) {
candidates.push_back(fn);
}
}
std::sort(candidates.begin(), candidates.end());
if (!candidates.empty()) {
std::string fn = base::join_path(dir, candidates.back());
if (base::is_file(fn)) {
ui::execute_from_ui_thread(
[this, fn]{
m_dumpFilename = fn;
if (auto app = App::instance()) {
app->memoryDumpFilename(fn);
app->showNotification(this);
}
});
}
}
}
});
#endif #endif
} }
@ -48,15 +118,24 @@ void SendCrash::notificationClick()
app::gen::SendCrash dlg; app::gen::SendCrash dlg;
// The current version is a "development" version if the VERSION #if _WIN32
// macro contains the "dev" word. // Only on Windows, if the current version is a "development"
// version (i.e. the VERSION macro contains the "dev" word), the
// .dmp file is useless for us. This is because we need the .exe +
// .pdb + source code used in the compilation process to make some
// sense of the .dmp file.
bool isDev = (std::string(VERSION).find("dev") != std::string::npos); bool isDev = (std::string(VERSION).find("dev") != std::string::npos);
if (isDev) { if (isDev) {
dlg.official()->setVisible(false); dlg.official()->setVisible(false);
dlg.devFilename()->setText(m_dumpFilename); dlg.devFilename()->setText(m_dumpFilename);
dlg.devFilename()->Click.connect(base::Bind(&SendCrash::onClickDevFilename, this)); dlg.devFilename()->Click.connect(base::Bind(&SendCrash::onClickDevFilename, this));
} }
else { else
#endif // On other platforms the crash file might be useful even in
// the -dev version (e.g. on macOS it's a text file with stack
// traces).
{
dlg.dev()->setVisible(false); dlg.dev()->setVisible(false);
dlg.filename()->setText(m_dumpFilename); dlg.filename()->setText(m_dumpFilename);
dlg.filename()->Click.connect(base::Bind(&SendCrash::onClickFilename, this)); dlg.filename()->Click.connect(base::Bind(&SendCrash::onClickFilename, this));

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -9,6 +10,7 @@
#pragma once #pragma once
#include "app/notification_delegate.h" #include "app/notification_delegate.h"
#include "app/task.h"
#include <string> #include <string>
@ -16,6 +18,10 @@ namespace app {
class SendCrash : public INotificationDelegate { class SendCrash : public INotificationDelegate {
public: public:
static std::string DefaultMemoryDumpFilename();
~SendCrash();
void search(); void search();
virtual std::string notificationText() override; virtual std::string notificationText() override;
@ -25,6 +31,7 @@ namespace app {
void onClickFilename(); void onClickFilename();
void onClickDevFilename(); void onClickDevFilename();
Task m_task;
std::string m_dumpFilename; std::string m_dumpFilename;
}; };

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -11,6 +11,7 @@
#include "app/task.h" #include "app/task.h"
#include "base/task.h" #include "base/task.h"
#include "base/thread.h"
#include "base/thread_pool.h" #include "base/thread_pool.h"
namespace app { namespace app {
@ -32,4 +33,12 @@ void Task::run(base::task::func_t&& func)
m_token = &m_task.start(tasks_pool); m_token = &m_task.start(tasks_pool);
} }
void Task::wait()
{
// TODO wait a condition variable
while (!m_task.completed()) {
base::this_thread::sleep_for(0.1);
}
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -20,6 +20,7 @@ namespace app {
~Task(); ~Task();
void run(base::task::func_t&& func); void run(base::task::func_t&& func);
void wait();
// Returns true when the task is completed (whether it was // Returns true when the task is completed (whether it was
// canceled or not) // canceled or not)
@ -27,6 +28,10 @@ namespace app {
return m_task.completed(); return m_task.completed();
} }
bool running() const {
return m_task.running();
}
bool canceled() const { bool canceled() const {
if (m_token) if (m_token)
return m_token->canceled(); return m_token->canceled();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -59,7 +59,7 @@ int app_main(int argc, char* argv[])
std::srand(static_cast<unsigned int>(std::time(nullptr))); std::srand(static_cast<unsigned int>(std::time(nullptr)));
#ifdef _WIN32 #ifdef _WIN32
::CoInitialize(NULL); ::CoInitialize(nullptr);
#endif #endif
try { try {
@ -70,11 +70,12 @@ int app_main(int argc, char* argv[])
os::ScopedHandle<os::System> system(os::create_system()); os::ScopedHandle<os::System> system(os::create_system());
app::App app; app::App app;
// Change the name of the memory dump file // Change the memory dump filename to save on disk (.dmp
// file). Note: Only useful on Windows.
{ {
const std::string filename = app::memory_dump_filename(); const std::string fn = app::SendCrash::DefaultMemoryDumpFilename();
if (!filename.empty()) if (!fn.empty())
memoryDump.setFileName(filename); memoryDump.setFileName(fn);
} }
const int code = app.initialize(options); const int code = app.initialize(options);