Revert "Use make_shared<>."
[dcpomatic.git] / src / tools / dcpomatic_kdm.cc
1 /*
2     Copyright (C) 2015 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 #include "wx/config_dialog.h"
22 #include "wx/about_dialog.h"
23 #include "wx/report_problem_dialog.h"
24 #include "wx/file_picker_ctrl.h"
25 #include "wx/wx_util.h"
26 #include "wx/wx_signal_manager.h"
27 #include "wx/screens_panel.h"
28 #include "wx/kdm_timing_panel.h"
29 #include "wx/kdm_output_panel.h"
30 #include "wx/job_view_dialog.h"
31 #include "wx/file_dialog_wrapper.h"
32 #include "wx/editable_list.h"
33 #include "lib/config.h"
34 #include "lib/util.h"
35 #include "lib/screen.h"
36 #include "lib/job_manager.h"
37 #include "lib/screen_kdm.h"
38 #include "lib/exceptions.h"
39 #include "lib/cinema_kdms.h"
40 #include "lib/send_kdm_email_job.h"
41 #include "lib/compose.hpp"
42 #include "lib/cinema.h"
43 #include <dcp/encrypted_kdm.h>
44 #include <dcp/decrypted_kdm.h>
45 #include <dcp/exceptions.h>
46 #include <wx/wx.h>
47 #include <wx/preferences.h>
48 #include <wx/filepicker.h>
49 #ifdef __WXOSX__
50 #include <ApplicationServices/ApplicationServices.h>
51 #endif
52 #include <boost/bind.hpp>
53 #include <boost/foreach.hpp>
54
55 #ifdef check
56 #undef check
57 #endif
58
59 using std::exception;
60 using std::list;
61 using std::string;
62 using std::vector;
63 using boost::shared_ptr;
64 using boost::bind;
65
66 enum {
67         ID_help_report_a_problem = 1,
68 };
69
70 class KDMFileDialogWrapper : public FileDialogWrapper<dcp::EncryptedKDM>
71 {
72 public:
73         KDMFileDialogWrapper (wxWindow* parent)
74                 : FileDialogWrapper<dcp::EncryptedKDM> (parent, _("Select DKDM file"))
75         {
76
77         }
78 };
79
80 static string
81 column (dcp::EncryptedKDM k)
82 {
83         return String::compose ("%1 (%2)", k.content_title_text(), k.cpl_id());
84 }
85
86 class DOMFrame : public wxFrame
87 {
88 public:
89         DOMFrame (wxString const & title)
90                 : wxFrame (0, -1, title)
91                 , _config_dialog (0)
92                 , _job_view (0)
93         {
94 #if defined(DCPOMATIC_WINDOWS)
95                 if (Config::instance()->win32_console ()) {
96                         AllocConsole();
97
98                         HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
99                         int hCrt = _open_osfhandle((intptr_t) handle_out, _O_TEXT);
100                         FILE* hf_out = _fdopen(hCrt, "w");
101                         setvbuf(hf_out, NULL, _IONBF, 1);
102                         *stdout = *hf_out;
103
104                         HANDLE handle_in = GetStdHandle(STD_INPUT_HANDLE);
105                         hCrt = _open_osfhandle((intptr_t) handle_in, _O_TEXT);
106                         FILE* hf_in = _fdopen(hCrt, "r");
107                         setvbuf(hf_in, NULL, _IONBF, 128);
108                         *stdin = *hf_in;
109
110                         std::cout << "DCP-o-matic KDM creator is starting." << "\n";
111                 }
112 #endif
113
114                 wxMenuBar* bar = new wxMenuBar;
115                 setup_menu (bar);
116                 SetMenuBar (bar);
117
118                 Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::file_exit, this),             wxID_EXIT);
119                 Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::edit_preferences, this),      wxID_PREFERENCES);
120                 Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::help_about, this),            wxID_ABOUT);
121                 Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&DOMFrame::help_report_a_problem, this), ID_help_report_a_problem);
122
123                 /* Use a panel as the only child of the Frame so that we avoid
124                    the dark-grey background on Windows.
125                 */
126                 wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
127                 wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
128
129                 wxBoxSizer* vertical = new wxBoxSizer (wxVERTICAL);
130
131                 wxFont subheading_font (*wxNORMAL_FONT);
132                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
133
134                 wxStaticText* h = new wxStaticText (overall_panel, wxID_ANY, _("Screens"));
135                 h->SetFont (subheading_font);
136                 vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL);
137                 _screens = new ScreensPanel (overall_panel);
138                 vertical->Add (_screens, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_Y_GAP);
139
140                 _timing = new KDMTimingPanel (overall_panel);
141                 vertical->Add (_timing, 0, wxALL, DCPOMATIC_SIZER_Y_GAP);
142
143                 h = new wxStaticText (overall_panel, wxID_ANY, _("DKDM"));
144                 h->SetFont (subheading_font);
145                 vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
146
147                 vector<string> columns;
148                 columns.push_back (wx_to_std (_("CPL")));
149                 _dkdm = new EditableList<dcp::EncryptedKDM, KDMFileDialogWrapper> (
150                         overall_panel, columns, bind (&DOMFrame::dkdms, this), bind (&DOMFrame::set_dkdms, this, _1), bind (&always_valid), bind (&column, _1), false
151                         );
152                 vertical->Add (_dkdm, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_Y_GAP);
153
154                 h = new wxStaticText (overall_panel, wxID_ANY, _("Output"));
155                 h->SetFont (subheading_font);
156                 vertical->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
157                 /* XXX: hard-coded non-interop here */
158                 _output = new KDMOutputPanel (overall_panel, false);
159                 vertical->Add (_output, 0, wxALL, DCPOMATIC_SIZER_Y_GAP);
160
161                 _create = new wxButton (overall_panel, wxID_ANY, _("Create KDMs"));
162                 vertical->Add (_create, 0, wxALL, DCPOMATIC_SIZER_GAP);
163
164                 main_sizer->Add (vertical, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
165                 overall_panel->SetSizer (main_sizer);
166
167                 /* Instantly save any config changes when using a DCP-o-matic GUI */
168                 Config::instance()->Changed.connect (boost::bind (&Config::write, Config::instance ()));
169
170                 _screens->ScreensChanged.connect (boost::bind (&DOMFrame::setup_sensitivity, this));
171                 _create->Bind (wxEVT_COMMAND_BUTTON_CLICKED, bind (&DOMFrame::create_kdms, this));
172                 _dkdm->SelectionChanged.connect (boost::bind (&DOMFrame::setup_sensitivity, this));
173
174                 setup_sensitivity ();
175         }
176
177 private:
178         vector<dcp::EncryptedKDM> dkdms () const
179         {
180                 return Config::instance()->dkdms ();
181         }
182
183         void set_dkdms (vector<dcp::EncryptedKDM> dkdms)
184         {
185                 Config::instance()->set_dkdms (dkdms);
186         }
187
188         void file_exit ()
189         {
190                 /* false here allows the close handler to veto the close request */
191                 Close (false);
192         }
193
194         void edit_preferences ()
195         {
196                 if (!_config_dialog) {
197                         _config_dialog = create_config_dialog ();
198                 }
199                 _config_dialog->Show (this);
200         }
201
202         void help_about ()
203         {
204                 AboutDialog* d = new AboutDialog (this);
205                 d->ShowModal ();
206                 d->Destroy ();
207         }
208
209         void help_report_a_problem ()
210         {
211                 ReportProblemDialog* d = new ReportProblemDialog (this, shared_ptr<Film> ());
212                 if (d->ShowModal () == wxID_OK) {
213                         d->report ();
214                 }
215                 d->Destroy ();
216         }
217
218         void setup_menu (wxMenuBar* m)
219         {
220                 wxMenu* file = new wxMenu;
221
222 #ifdef __WXOSX__
223                 file->Append (wxID_EXIT, _("&Exit"));
224 #else
225                 file->Append (wxID_EXIT, _("&Quit"));
226 #endif
227
228 #ifdef __WXOSX__
229                 file->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
230 #else
231                 wxMenu* edit = new wxMenu;
232                 edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
233 #endif
234
235                 wxMenu* help = new wxMenu;
236 #ifdef __WXOSX__
237                 help->Append (wxID_ABOUT, _("About DCP-o-matic"));
238 #else
239                 help->Append (wxID_ABOUT, _("About"));
240 #endif
241                 help->Append (ID_help_report_a_problem, _("Report a problem..."));
242
243                 m->Append (file, _("&File"));
244 #ifndef __WXOSX__
245                 m->Append (edit, _("&Edit"));
246 #endif
247                 m->Append (help, _("&Help"));
248         }
249
250         void create_kdms ()
251         {
252                 try {
253                         if (!_dkdm->selection()) {
254                                 return;
255                         }
256
257                         /* Decrypt the DKDM */
258                         dcp::DecryptedKDM decrypted (_dkdm->selection().get(), Config::instance()->decryption_chain()->key().get());
259
260                         /* This is the signer for our new KDMs */
261                         shared_ptr<const dcp::CertificateChain> signer = Config::instance()->signer_chain ();
262                         if (!signer->valid ()) {
263                                 throw InvalidSignerError ();
264                         }
265
266                         list<ScreenKDM> screen_kdms;
267                         BOOST_FOREACH (shared_ptr<Screen> i, _screens->screens()) {
268
269                                 if (!i->recipient) {
270                                         continue;
271                                 }
272
273                                 /* Make an empty KDM */
274                                 dcp::DecryptedKDM kdm (
275                                         dcp::LocalTime (_timing->from(), i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
276                                         dcp::LocalTime (_timing->until(), i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
277                                         decrypted.annotation_text().get_value_or (""),
278                                         decrypted.content_title_text(),
279                                         dcp::LocalTime().as_string()
280                                         );
281
282                                 /* Add keys from the DKDM */
283                                 BOOST_FOREACH (dcp::DecryptedKDMKey const & j, decrypted.keys()) {
284                                         kdm.add_key (j);
285                                 }
286
287                                 /* Encrypt */
288                                 screen_kdms.push_back (ScreenKDM (i, kdm.encrypt (signer, i->recipient.get(), i->trusted_devices, _output->formulation())));
289                         }
290
291                         if (_output->write_to()) {
292                                 ScreenKDM::write_files (decrypted.content_title_text(), screen_kdms, _output->directory());
293                                 /* XXX: proper plural form support in wxWidgets? */
294                                 wxString s = screen_kdms.size() == 1 ? _("%d KDM written to %s") : _("%d KDMs written to %s");
295                                 message_dialog (
296                                         this,
297                                         wxString::Format (s, int(screen_kdms.size()), std_to_wx(_output->directory().string()).data())
298                                         );
299                         } else {
300                                 string film_name = decrypted.annotation_text().get_value_or ("");
301                                 if (film_name.empty ()) {
302                                         film_name = decrypted.content_title_text ();
303                                 }
304                                 shared_ptr<Job> job (new SendKDMEmailJob (
305                                                              film_name,
306                                                              decrypted.content_title_text(),
307                                                              _timing->from(), _timing->until(),
308                                                              CinemaKDMs::collect (screen_kdms),
309                                                              shared_ptr<Log> ()
310                                                              ));
311
312                                 JobManager::instance()->add (job);
313                                 if (_job_view) {
314                                         _job_view->Destroy ();
315                                         _job_view = 0;
316                                 }
317                                 _job_view = new JobViewDialog (this, _("Send KDM emails"), job);
318                                 _job_view->ShowModal ();
319                         }
320                 } catch (dcp::NotEncryptedError& e) {
321                         error_dialog (this, _("CPL's content is not encrypted."));
322                 } catch (exception& e) {
323                         error_dialog (this, e.what ());
324                 } catch (...) {
325                         error_dialog (this, _("An unknown exception occurred."));
326                 }
327         }
328
329         void setup_sensitivity ()
330         {
331                 _screens->setup_sensitivity ();
332                 _output->setup_sensitivity ();
333                 _create->Enable (!_screens->screens().empty() && _dkdm->selection());
334         }
335
336         wxPreferencesEditor* _config_dialog;
337         ScreensPanel* _screens;
338         KDMTimingPanel* _timing;
339         EditableList<dcp::EncryptedKDM, KDMFileDialogWrapper>* _dkdm;
340         wxButton* _create;
341         KDMOutputPanel* _output;
342         JobViewDialog* _job_view;
343 };
344
345 /** @class App
346  *  @brief The magic App class for wxWidgets.
347  */
348 class App : public wxApp
349 {
350 public:
351         App ()
352                 : wxApp ()
353                 , _frame (0)
354         {}
355
356 private:
357
358         bool OnInit ()
359         try
360         {
361                 wxInitAllImageHandlers ();
362
363                 SetAppName (_("DCP-o-matic KDM Creator"));
364
365                 if (!wxApp::OnInit()) {
366                         return false;
367                 }
368
369 #ifdef DCPOMATIC_LINUX
370                 unsetenv ("UBUNTU_MENUPROXY");
371 #endif
372
373 #ifdef __WXOSX__
374                 ProcessSerialNumber serial;
375                 GetCurrentProcess (&serial);
376                 TransformProcessType (&serial, kProcessTransformToForegroundApplication);
377 #endif
378
379                 dcpomatic_setup_path_encoding ();
380
381                 /* Enable i18n; this will create a Config object
382                    to look for a force-configured language.  This Config
383                    object will be wrong, however, because dcpomatic_setup
384                    hasn't yet been called and there aren't any filters etc.
385                    set up yet.
386                 */
387                 dcpomatic_setup_i18n ();
388
389                 /* Set things up, including filters etc.
390                    which will now be internationalised correctly.
391                 */
392                 dcpomatic_setup ();
393
394                 /* Force the configuration to be re-loaded correctly next
395                    time it is needed.
396                 */
397                 Config::drop ();
398
399                 _frame = new DOMFrame (_("DCP-o-matic KDM Creator"));
400                 SetTopWindow (_frame);
401                 _frame->Maximize ();
402                 _frame->Show ();
403
404                 signal_manager = new wxSignalManager (this);
405                 Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
406
407                 return true;
408         }
409         catch (exception& e)
410         {
411                 error_dialog (0, wxString::Format ("DCP-o-matic could not start: %s", e.what ()));
412                 return true;
413         }
414
415         /* An unhandled exception has occurred inside the main event loop */
416         bool OnExceptionInMainLoop ()
417         {
418                 try {
419                         throw;
420                 } catch (FileError& e) {
421                         error_dialog (
422                                 0,
423                                 wxString::Format (
424                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
425                                         std_to_wx (e.what()),
426                                         std_to_wx (e.file().string().c_str ())
427                                         )
428                                 );
429                 } catch (exception& e) {
430                         error_dialog (
431                                 0,
432                                 wxString::Format (
433                                         _("An exception occurred: %s.\n\n") + " " + REPORT_PROBLEM,
434                                         std_to_wx (e.what ())
435                                         )
436                                 );
437                 } catch (...) {
438                         error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
439                 }
440
441                 /* This will terminate the program */
442                 return false;
443         }
444
445         void OnUnhandledException ()
446         {
447                 error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
448         }
449
450         void idle ()
451         {
452                 signal_manager->ui_idle ();
453         }
454
455         DOMFrame* _frame;
456 };
457
458 IMPLEMENT_APP (App)