Give an error if a non-DCP folder is selected for DCP writing (#2024).
[dcpomatic.git] / src / tools / dcpomatic_disk.cc
index 92429aa53238c43a75a57bae33328750c895696a..7e43c068469ae74149671ca1124971e9d7f46727 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2019-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
-#include "wx/wx_signal_manager.h"
-#include "wx/wx_util.h"
-#include "wx/job_manager_view.h"
+
+#include "wx/disk_warning_dialog.h"
 #include "wx/drive_wipe_warning_dialog.h"
-#include "wx/try_unmount_dialog.h"
+#include "wx/job_manager_view.h"
 #include "wx/message_dialog.h"
-#include "wx/disk_warning_dialog.h"
-#include "lib/file_log.h"
-#include "lib/dcpomatic_log.h"
-#include "lib/util.h"
+#include "wx/try_unmount_dialog.h"
+#include "wx/wx_util.h"
+#include "wx/wx_signal_manager.h"
+#include "wx/wx_util.h"
 #include "lib/config.h"
-#include "lib/signal_manager.h"
-#include "lib/cross.h"
 #include "lib/copy_to_drive_job.h"
-#include "lib/job_manager.h"
+#include "lib/cross.h"
+#include "lib/dcpomatic_log.h"
 #include "lib/disk_writer_messages.h"
+#include "lib/file_log.h"
+#include "lib/job_manager.h"
+#include "lib/signal_manager.h"
+#include "lib/util.h"
 #include "lib/version.h"
 #include "lib/warnings.h"
+#include <wx/cmdline.h>
 #include <wx/wx.h>
 DCPOMATIC_DISABLE_WARNINGS
 #include <boost/process.hpp>
@@ -47,38 +50,56 @@ DCPOMATIC_ENABLE_WARNINGS
 #include <notify.h>
 #endif
 
-using std::string;
-using std::exception;
-using std::cout;
+
 using std::cerr;
-using boost::shared_ptr;
+using std::cout;
+using std::exception;
+using std::make_shared;
+using std::shared_ptr;
+using std::string;
 using boost::optional;
 #if BOOST_VERSION >= 106100
 using namespace boost::placeholders;
 #endif
 
 
+#ifdef DCPOMATIC_OSX
+enum {
+       ID_tools_uninstall = 1,
+};
+#endif
+
+
 class DOMFrame : public wxFrame
 {
 public:
        explicit DOMFrame (wxString const & title)
-               : wxFrame (0, -1, title)
+               : wxFrame (nullptr, wxID_ANY, title)
                , _nanomsg (true)
                , _sizer (new wxBoxSizer(wxVERTICAL))
        {
+#ifdef DCPOMATIC_OSX
+               auto bar = new wxMenuBar;
+               auto tools = new wxMenu;
+               tools->Append(ID_tools_uninstall, _("Uninstall..."));
+               bar->Append(tools, _("Tools"));
+               SetMenuBar (bar);
+               Bind (wxEVT_MENU, boost::bind(&DOMFrame::uninstall, this), ID_tools_uninstall);
+#endif
+
                /* Use a panel as the only child of the Frame so that we avoid
                   the dark-grey background on Windows.
                */
-               wxPanel* overall_panel = new wxPanel (this);
-               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               auto overall_panel = new wxPanel (this);
+               auto s = new wxBoxSizer (wxHORIZONTAL);
                s->Add (overall_panel, 1, wxEXPAND);
                SetSizer (s);
 
-               wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 
                int r = 0;
                add_label_to_sizer (grid, overall_panel, _("DCP"), true, wxGBPosition(r, 0));
-               wxBoxSizer* dcp_name_sizer = new wxBoxSizer (wxHORIZONTAL);
+               auto dcp_name_sizer = new wxBoxSizer (wxHORIZONTAL);
                _dcp_name = new wxStaticText (overall_panel, wxID_ANY, wxEmptyString);
                dcp_name_sizer->Add (_dcp_name, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
                _dcp_open = new wxButton (overall_panel, wxID_ANY, _("Open..."));
@@ -87,7 +108,7 @@ public:
                ++r;
 
                add_label_to_sizer (grid, overall_panel, _("Drive"), true, wxGBPosition(r, 0));
-               wxBoxSizer* drive_sizer = new wxBoxSizer (wxHORIZONTAL);
+               auto drive_sizer = new wxBoxSizer (wxHORIZONTAL);
                _drive = new wxChoice (overall_panel, wxID_ANY);
                drive_sizer->Add (_drive, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
                _drive_refresh = new wxButton (overall_panel, wxID_ANY, _("Refresh"));
@@ -113,7 +134,7 @@ public:
                _sizer->Add (grid, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER);
                overall_panel->SetSizer (_sizer);
                Fit ();
-               SetSize (768, GetSize().GetHeight() + 32);
+               SetSize (1024, GetSize().GetHeight() + 32);
 
                /* XXX: this is a hack, but I expect we'll need logs and I'm not sure if there's
                 * a better place to put them.
@@ -153,6 +174,21 @@ public:
        ~DOMFrame ()
        {
                _nanomsg.send(DISK_WRITER_QUIT "\n", 2000);
+               /* This seems really horrible but it's suggested by the examples on nanomsg.org, so...
+                * Without this the quit is not received (at least sometimes) causing #2018.
+                */
+               dcpomatic_sleep_seconds (1);
+       }
+
+       void set_dcp (boost::filesystem::path dcp)
+       {
+               if (!boost::filesystem::exists(dcp / "ASSETMAP") && !boost::filesystem::exists(dcp / "ASSETMAP.xml")) {
+                       error_dialog (nullptr, _("No ASSETMAP or ASSETMAP.xml found in this folder.  Please choose a DCP folder."));
+                       return;
+               }
+
+               _dcp_path = dcp;
+               _dcp_name->SetLabel (std_to_wx(dcp.filename().string()));
        }
 
 private:
@@ -163,13 +199,21 @@ private:
        }
 
 
+#ifdef DCPOMATIC_OSX
+       void uninstall()
+       {
+               system(String::compose("osascript \"%1/uninstall_disk.applescript\"", resources_path().string()).c_str());
+       }
+#endif
+
+
        bool should_close ()
        {
                if (!JobManager::instance()->work_to_do()) {
                        return true;
                }
 
-               wxMessageDialog* d = new wxMessageDialog (
+               auto d = new wxMessageDialog (
                        0,
                        _("There are unfinished jobs; are you sure you want to quit?"),
                        _("Unfinished jobs"),
@@ -195,7 +239,7 @@ private:
 
        void open ()
        {
-               wxDirDialog* d = new wxDirDialog (this, _("Choose a DCP folder"), wxT(""), wxDD_DIR_MUST_EXIST);
+               auto d = new wxDirDialog (this, _("Choose a DCP folder"), wxT(""), wxDD_DIR_MUST_EXIST);
                int r = d->ShowModal ();
                boost::filesystem::path const path (wx_to_std(d->GetPath()));
                d->Destroy ();
@@ -204,29 +248,50 @@ private:
                        return;
                }
 
-               _dcp_path = path;
-               _dcp_name->SetLabel (std_to_wx(_dcp_path->filename().string()));
+               set_dcp (path);
                setup_sensitivity ();
        }
 
        void copy ()
        {
+               /* Check that the selected drive still exists and update its properties if so */
+               drive_refresh ();
+               if (_drive->GetSelection() == wxNOT_FOUND) {
+                       error_dialog (this, _("The disk you selected is no longer available.  Please choose another."));
+                       return;
+               }
+
                DCPOMATIC_ASSERT (_drive->GetSelection() != wxNOT_FOUND);
                DCPOMATIC_ASSERT (static_cast<bool>(_dcp_path));
 
-               bool have_writer = true;
-               if (!_nanomsg.send(DISK_WRITER_PING "\n", 2000)) {
-                       have_writer = false;
-               } else {
-                       optional<string> reply = _nanomsg.receive (2000);
-                       if (!reply || *reply != DISK_WRITER_PONG) {
-                               have_writer = false;
+               auto ping = [this](int attempt) {
+                       if (_nanomsg.send(DISK_WRITER_PING "\n", 1000)) {
+                               auto reply = _nanomsg.receive (1000);
+                               if (reply && *reply == DISK_WRITER_PONG) {
+                                       return true;
+                               } else if (reply) {
+                                       LOG_DISK("Unexpected response %1 to ping received (attempt %2)", *reply, attempt);
+                               } else {
+                                       LOG_DISK("No reply received from ping (attempt %1)", attempt);
+                               }
+                       } else {
+                               LOG_DISK("Could not send ping to writer (attempt %1)", attempt);
+                       }
+                       dcpomatic_sleep_seconds (1);
+                       return false;
+               };
+
+               bool have_writer = false;
+               for (int i = 0; i < 8; ++i) {
+                       if (ping(i + 1)) {
+                               have_writer = true;
+                               break;
                        }
                }
 
                if (!have_writer) {
-#ifdef DCPOMATIC_WINDOWS
-                       MessageDialog* m = new MessageDialog (
+#if defined(DCPOMATIC_WINDOWS)
+                       auto m = new MessageDialog (
                                this,
                                _("DCP-o-matic Disk Writer"),
                                _("Do you see a 'User Account Control' dialogue asking about dcpomatic2_disk_writer.exe?  If so, click 'Yes', then try again.")
@@ -234,14 +299,24 @@ private:
                        m->ShowModal ();
                        m->Destroy ();
                        return;
+#elif defined(DCPOMATIC_OSX)
+                       auto m = new MessageDialog (
+                               this,
+                               _("DCP-o-matic Disk Writer"),
+                               _("Did you install the DCP-o-matic Disk Writer.pkg from the .dmg?  Please check and try again.")
+                               );
+                       m->ShowModal ();
+                       m->Destroy ();
+                       return;
 #else
+                       LOG_DISK_NC ("Failed to ping writer");
                        throw CommunicationFailedError ();
 #endif
                }
 
-               Drive const& drive = _drives[_drive->GetSelection()];
+               auto const& drive = _drives[_drive->GetSelection()];
                if (drive.mounted()) {
-                       TryUnmountDialog* d = new TryUnmountDialog(this, drive.description());
+                       auto d = new TryUnmountDialog(this, drive.description());
                        int const r = d->ShowModal ();
                        d->Destroy ();
                        if (r != wxID_OK) {
@@ -250,14 +325,17 @@ private:
 
                        LOG_DISK("Sending unmount request to disk writer for %1", drive.as_xml());
                        if (!_nanomsg.send(DISK_WRITER_UNMOUNT "\n", 2000)) {
+                               LOG_DISK_NC("Failed to send unmount request.");
                                throw CommunicationFailedError ();
                        }
                        if (!_nanomsg.send(drive.as_xml(), 2000)) {
+                               LOG_DISK_NC("Failed to send drive for unmount request.");
                                throw CommunicationFailedError ();
                        }
-                       optional<string> reply = _nanomsg.receive (2000);
+                       /* The reply may have to wait for the user to authenticate, so let's wait a while */
+                       auto reply = _nanomsg.receive (30000);
                        if (!reply || *reply != DISK_WRITER_OK) {
-                               MessageDialog* m = new MessageDialog (
+                               auto * m = new MessageDialog (
                                                this,
                                                _("DCP-o-matic Disk Writer"),
                                                wxString::Format(_("The drive %s could not be unmounted.\nClose any application that is using it, then try again."), std_to_wx(drive.description()))
@@ -269,7 +347,7 @@ private:
                }
 
 
-               DriveWipeWarningDialog* d = new DriveWipeWarningDialog (this, _drive->GetString(_drive->GetSelection()));
+               auto * d = new DriveWipeWarningDialog (this, _drive->GetString(_drive->GetSelection()));
                int const r = d->ShowModal ();
                bool ok = r == wxID_OK && d->confirmed();
                d->Destroy ();
@@ -278,7 +356,7 @@ private:
                        return;
                }
 
-               JobManager::instance()->add(shared_ptr<Job>(new CopyToDriveJob(*_dcp_path, _drives[_drive->GetSelection()], _nanomsg)));
+               JobManager::instance()->add(make_shared<CopyToDriveJob>(*_dcp_path, _drives[_drive->GetSelection()], _nanomsg));
                setup_sensitivity ();
        }
 
@@ -293,8 +371,8 @@ private:
                int re_select = wxNOT_FOUND;
                int j = 0;
                _drives = Drive::get ();
-               BOOST_FOREACH (Drive i, _drives) {
-                       wxString const s = std_to_wx(i.description());
+               for (auto i: _drives) {
+                       auto const s = std_to_wx(i.description());
                        if (s == current) {
                                re_select = j;
                        }
@@ -325,11 +403,19 @@ private:
        wxSizer* _sizer;
 };
 
+
+static const wxCmdLineEntryDesc command_line_description[] = {
+       { wxCMD_LINE_OPTION, "d", "dcp", "DCP to write", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_SWITCH, "s", "sure", "skip alpha test warnings", wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL },
+       { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
+};
+
+
 class App : public wxApp
 {
 public:
        App ()
-               : _frame (0)
+               : _frame (nullptr)
        {}
 
        bool OnInit ()
@@ -349,6 +435,7 @@ public:
 #endif
 
 #ifdef DCPOMATIC_OSX
+                       dcpomatic_sleep_seconds (1);
                        make_foreground_application ();
 #endif
 
@@ -372,18 +459,24 @@ public:
                        */
                        Config::drop ();
 
-                       DiskWarningDialog* warning = new DiskWarningDialog ();
-                       warning->ShowModal ();
-                       if (!warning->confirmed()) {
-                               return false;
+                       if (!_skip_alpha_check) {
+                               auto warning = new DiskWarningDialog ();
+                               warning->ShowModal ();
+                               if (!warning->confirmed()) {
+                                       return false;
+                               }
+                               warning->Destroy ();
                        }
-                       warning->Destroy ();
 
                        _frame = new DOMFrame (_("DCP-o-matic Disk Writer"));
                        SetTopWindow (_frame);
 
                        _frame->Show ();
 
+                       if (_dcp_to_write) {
+                               _frame->set_dcp (*_dcp_to_write);
+                       }
+
                        signal_manager = new wxSignalManager (this);
                        Bind (wxEVT_IDLE, boost::bind (&App::idle, this, _1));
                }
@@ -396,6 +489,24 @@ public:
                return true;
        }
 
+       void OnInitCmdLine (wxCmdLineParser& parser)
+       {
+               parser.SetDesc (command_line_description);
+               parser.SetSwitchChars (wxT ("-"));
+       }
+
+       bool OnCmdLineParsed (wxCmdLineParser& parser)
+       {
+               _skip_alpha_check = parser.Found(wxT("sure"));
+
+               wxString dcp;
+               if (parser.Found(wxT("dcp"), &dcp)) {
+                       _dcp_to_write = wx_to_std (dcp);
+               }
+
+               return true;
+       }
+
        void config_failed_to_load ()
        {
                message_dialog (_frame, _("The existing configuration failed to load.  Default values will be used instead.  These may take a short time to create."));
@@ -451,6 +562,8 @@ public:
        }
 
        DOMFrame* _frame;
+       bool _skip_alpha_check = false;
+       boost::optional<boost::filesystem::path> _dcp_to_write;
 };
 
 IMPLEMENT_APP (App)