Separate GUI verifier with basic reporting (#1823).
[dcpomatic.git] / src / tools / dcpomatic_verifier.cc
1 /*
2     Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 /** @file  src/tools/dcpomatic_verify.cc
23  *  @brief A DCP verify GUI.
24  */
25
26
27 #include "wx/check_box.h"
28 #include "wx/dcpomatic_button.h"
29 #include "wx/dir_picker_ctrl.h"
30 #include "wx/verify_dcp_progress_panel.h"
31 #include "wx/verify_dcp_result_panel.h"
32 #include "wx/wx_util.h"
33 #include "lib/constants.h"
34 #include "lib/cross.h"
35 #include "lib/job_manager.h"
36 #include "lib/verify_dcp_job.h"
37 #include "lib/util.h"
38 #include <dcp/verify_report.h>
39 LIBDCP_DISABLE_WARNINGS
40 #include <wx/evtloop.h>
41 #include <wx/wx.h>
42 LIBDCP_ENABLE_WARNINGS
43 #ifdef __WXGTK__
44 #include <X11/Xlib.h>
45 #endif
46
47
48 using std::exception;
49 using std::make_shared;
50
51
52 class DOMFrame : public wxFrame
53 {
54 public:
55         explicit DOMFrame(wxString const& title)
56                 : wxFrame(nullptr, -1, title)
57         {
58 #ifdef DCPOMATIC_WINDOWS
59                 SetIcon(wxIcon(std_to_wx("id")));
60 #endif
61                 auto overall_sizer = new wxBoxSizer(wxVERTICAL);
62
63                 auto dcp_sizer = new wxBoxSizer(wxHORIZONTAL);
64                 add_label_to_sizer(dcp_sizer, this, _("DCP"), true, 0, wxALIGN_CENTER_VERTICAL);
65                 _dcp = new DirPickerCtrl(this, true);
66                 dcp_sizer->Add(_dcp, 1, wxEXPAND);
67                 overall_sizer->Add(dcp_sizer, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
68
69                 auto options_sizer = new wxBoxSizer(wxVERTICAL);
70                 _write_log = new CheckBox(this, _("Write log to DCP folder"));
71                 options_sizer->Add(_write_log, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
72                 overall_sizer->Add(options_sizer, 0, wxLEFT, DCPOMATIC_DIALOG_BORDER);
73
74                 _verify = new Button(this, _("Verify"));
75                 overall_sizer->Add(_verify, 0, wxEXPAND | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
76
77                 _progress_panel = new VerifyDCPProgressPanel(this);
78                 overall_sizer->Add(_progress_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
79
80                 _result_panel = new VerifyDCPResultPanel(this);
81                 overall_sizer->Add(_result_panel, 0, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
82
83                 SetSizerAndFit(overall_sizer);
84
85                 _dcp->Changed.connect(boost::bind(&DOMFrame::setup_sensitivity, this));
86                 _verify->bind(&DOMFrame::verify_clicked, this);
87
88                 setup_sensitivity();
89         }
90
91 private:
92         void setup_sensitivity()
93         {
94                 _verify->Enable(!_dcp->GetPath().IsEmpty());
95         }
96
97         void verify_clicked()
98         {
99                 auto dcp = boost::filesystem::path(wx_to_std(_dcp->GetPath()));
100                 if (dcp.empty()) {
101                         return;
102                 }
103
104                 auto job_manager = JobManager::instance();
105                 auto job = make_shared<VerifyDCPJob>(std::vector<boost::filesystem::path>{dcp}, std::vector<boost::filesystem::path>());
106                 job_manager->add(job);
107
108                 while (job_manager->work_to_do()) {
109                         wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
110                         dcpomatic_sleep_seconds(1);
111
112                         _progress_panel->update(job);
113                 }
114
115                 _result_panel->fill(job);
116                 if (_write_log->get()) {
117                         dcp::TextFormatter formatter(dcp / "REPORT.txt");
118                         dcp::verify_report(job->result(), formatter);
119                 }
120         }
121
122         DirPickerCtrl* _dcp;
123         CheckBox* _write_log;
124         Button* _verify;
125         VerifyDCPProgressPanel* _progress_panel;
126         VerifyDCPResultPanel* _result_panel;
127 };
128
129
130 /** @class App
131  *  @brief The magic App class for wxWidgets.
132  */
133 class App : public wxApp
134 {
135 public:
136         App()
137                 : wxApp()
138         {
139                 dcpomatic_setup_path_encoding();
140 #ifdef DCPOMATIC_LINUX
141                 XInitThreads();
142 #endif
143         }
144
145 private:
146         bool OnInit() override
147         {
148                 try {
149                         SetAppName(_("DCP-o-matic Verifier"));
150
151                         if (!wxApp::OnInit()) {
152                                 return false;
153                         }
154
155 #ifdef DCPOMATIC_LINUX
156                         unsetenv("UBUNTU_MENUPROXY");
157 #endif
158
159 #ifdef DCPOMATIC_OSX
160                         dcpomatic_sleep_seconds(1);
161                         make_foreground_application();
162 #endif
163
164                         /* Enable i18n; this will create a Config object
165                            to look for a force-configured language.  This Config
166                            object will be wrong, however, because dcpomatic_setup
167                            hasn't yet been called and there aren't any filters etc.
168                            set up yet.
169                         */
170                         dcpomatic_setup_i18n();
171
172                         /* Set things up, including filters etc.
173                            which will now be internationalised correctly.
174                         */
175                         dcpomatic_setup();
176
177                         /* Force the configuration to be re-loaded correctly next
178                            time it is needed.
179                         */
180                         Config::drop();
181
182                         _frame = new DOMFrame(_("DCP-o-matic Verifier"));
183                         SetTopWindow(_frame);
184                         _frame->SetSize({480, 640});
185                         _frame->Show();
186                 }
187                 catch (exception& e)
188                 {
189                         error_dialog(nullptr, wxString::Format("DCP-o-matic Verifier could not start."), std_to_wx(e.what()));
190                 }
191
192                 return true;
193         }
194
195         void report_exception()
196         {
197                 try {
198                         throw;
199                 } catch (FileError& e) {
200                         error_dialog(
201                                 nullptr,
202                                 wxString::Format(
203                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
204                                         std_to_wx(e.what()),
205                                         std_to_wx(e.file().string().c_str())
206                                         )
207                                 );
208                 } catch (boost::filesystem::filesystem_error& e) {
209                         error_dialog(
210                                 nullptr,
211                                 wxString::Format(
212                                         _("An exception occurred: %s (%s) (%s)\n\n") + REPORT_PROBLEM,
213                                         std_to_wx(e.what()),
214                                         std_to_wx(e.path1().string()),
215                                         std_to_wx(e.path2().string())
216                                         )
217                                 );
218                 } catch (exception& e) {
219                         error_dialog(
220                                 nullptr,
221                                 wxString::Format(
222                                         _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
223                                         std_to_wx(e.what())
224                                         )
225                                 );
226                 } catch (...) {
227                         error_dialog(nullptr, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
228                 }
229         }
230
231         /* An unhandled exception has occurred inside the main event loop */
232         bool OnExceptionInMainLoop() override
233         {
234                 report_exception();
235                 return false;
236         }
237
238         void OnUnhandledException() override
239         {
240                 report_exception();
241         }
242
243         DOMFrame* _frame = nullptr;
244 };
245
246
247 IMPLEMENT_APP(App)