1// Aseprite
2// Copyright (C) 2020 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/send_crash.h"
13
14#include "app/app.h"
15#include "app/console.h"
16#include "app/i18n/strings.h"
17#include "app/resource_finder.h"
18#include "app/task.h"
19#include "base/fs.h"
20#include "base/launcher.h"
21#include "fmt/format.h"
22#include "ui/alert.h"
23#include "ui/system.h"
24#include "ver/info.h"
25
26#include "send_crash.xml.h"
27
28namespace app {
29
30// static
31std::string SendCrash::DefaultMemoryDumpFilename()
32{
33#ifdef _WIN32
34 std::string kDefaultCrashName = fmt::format("{}-crash-{}.dmp",
35 get_app_name(),
36 get_app_version());
37 ResourceFinder rf;
38 rf.includeUserDir(kDefaultCrashName.c_str());
39 return rf.getFirstOrCreateDefault();
40#else
41 return std::string();
42#endif
43}
44
45SendCrash::~SendCrash()
46{
47 if (m_task.running()) {
48 m_task.cancel();
49 m_task.wait();
50 }
51}
52
53void SendCrash::search()
54{
55#ifdef _WIN32
56 // On Windows we use one mini-dump to report bugs, then we can open
57 // this .dmp file locally along with the .exe + .pdb to check the
58 // stack trace and detect the cause of the bug.
59
60 m_dumpFilename = SendCrash::DefaultMemoryDumpFilename();
61 if (!m_dumpFilename.empty() &&
62 base::is_file(m_dumpFilename)) {
63 auto app = App::instance();
64 app->memoryDumpFilename(m_dumpFilename);
65#ifdef ENABLE_UI
66 app->showNotification(this);
67#endif
68 }
69
70#elif defined(__APPLE__)
71 // On macOS we can show the possibility to send the latest crash
72 // report from ~/Library/Logs/DiagnosticReports which is the
73 // location where crash reports (.crash files) are located.
74
75 m_task.run(
76 [this](base::task_token&){
77 ResourceFinder rf;
78 rf.includeHomeDir("Library/Logs/DiagnosticReports");
79 std::string dir = rf.defaultFilename();
80 if (base::is_directory(dir)) {
81 std::vector<std::string> candidates;
82 int n = std::strlen(get_app_name());
83 for (const auto& fn : base::list_files(dir)) {
84 // Cancel everything
85 if (m_task.canceled())
86 return;
87
88 if (base::utf8_icmp(get_app_name(), fn, n) == 0) {
89 candidates.push_back(fn);
90 }
91 }
92 std::sort(candidates.begin(), candidates.end());
93 if (!candidates.empty()) {
94 std::string fn = base::join_path(dir, candidates.back());
95 if (base::is_file(fn)) {
96 ui::execute_from_ui_thread(
97 [this, fn]{
98 m_dumpFilename = fn;
99 if (auto app = App::instance()) {
100 app->memoryDumpFilename(fn);
101#ifdef ENABLE_UI
102 app->showNotification(this);
103#endif
104 }
105 });
106 }
107 }
108 }
109 });
110
111#endif
112}
113
114#ifdef ENABLE_UI
115
116std::string SendCrash::notificationText()
117{
118 return "Report last crash";
119}
120
121void SendCrash::notificationClick()
122{
123 if (m_dumpFilename.empty()) {
124 ui::Alert::show(Strings::alerts_nothing_to_report());
125 return;
126 }
127
128 app::gen::SendCrash dlg;
129
130#if _WIN32
131 // Only on Windows, if the current version is a development version
132 // (i.e. the get_app_version() contains "-dev"), the .dmp
133 // file is useless for us. This is because we need the .exe + .pdb +
134 // source code used in the compilation process to make some sense of
135 // the .dmp file.
136
137 bool isDev = (std::string(get_app_version()).find("-dev") != std::string::npos);
138 if (isDev) {
139 dlg.official()->setVisible(false);
140 dlg.devFilename()->setText(m_dumpFilename);
141 dlg.devFilename()->Click.connect([this]{ onClickDevFilename(); });
142 }
143 else
144#endif // On other platforms the crash file might be useful even in
145 // the "-dev" version (e.g. on macOS it's a text file with
146 // stack traces).
147 {
148 dlg.dev()->setVisible(false);
149 dlg.filename()->setText(m_dumpFilename);
150 dlg.filename()->Click.connect([this]{ onClickFilename(); });
151 }
152
153 dlg.openWindowInForeground();
154 if (dlg.closer() == dlg.deleteFile()) {
155 try {
156 base::delete_file(m_dumpFilename);
157 m_dumpFilename = "";
158 }
159 catch (const std::exception& ex) {
160 Console::showException(ex);
161 }
162 }
163}
164
165void SendCrash::onClickFilename()
166{
167 base::launcher::open_folder(m_dumpFilename);
168}
169
170void SendCrash::onClickDevFilename()
171{
172 base::launcher::open_file(m_dumpFilename);
173}
174
175#endif // ENABLE_UI
176
177} // namespace app
178