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 | |
28 | namespace app { |
29 | |
30 | // static |
31 | std::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 | |
45 | SendCrash::~SendCrash() |
46 | { |
47 | if (m_task.running()) { |
48 | m_task.cancel(); |
49 | m_task.wait(); |
50 | } |
51 | } |
52 | |
53 | void 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 | |
116 | std::string SendCrash::notificationText() |
117 | { |
118 | return "Report last crash" ; |
119 | } |
120 | |
121 | void 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 | |
165 | void SendCrash::onClickFilename() |
166 | { |
167 | base::launcher::open_folder(m_dumpFilename); |
168 | } |
169 | |
170 | void SendCrash::onClickDevFilename() |
171 | { |
172 | base::launcher::open_file(m_dumpFilename); |
173 | } |
174 | |
175 | #endif // ENABLE_UI |
176 | |
177 | } // namespace app |
178 | |