diff options
| author | Carl Hetherington <cth@carlh.net> | 2020-03-16 00:44:31 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2020-04-06 15:57:14 +0200 |
| commit | a1f7bf2d9e5610075fbd898cdf52f4f8373741f2 (patch) | |
| tree | 5539cea37bebe3347408b9404ac3d9aa5cd5fe1b /src/tools | |
| parent | adddda49c17e87198253d9c900dcef0f5fb2e175 (diff) | |
Add disk writer tool.
Diffstat (limited to 'src/tools')
| -rw-r--r-- | src/tools/dcpomatic_disk.cc | 309 | ||||
| -rw-r--r-- | src/tools/dcpomatic_disk_writer.cc | 474 | ||||
| -rw-r--r-- | src/tools/wscript | 15 |
3 files changed, 798 insertions, 0 deletions
diff --git a/src/tools/dcpomatic_disk.cc b/src/tools/dcpomatic_disk.cc new file mode 100644 index 000000000..baccdbce3 --- /dev/null +++ b/src/tools/dcpomatic_disk.cc @@ -0,0 +1,309 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "wx/wx_signal_manager.h" +#include "wx/wx_util.h" +#include "wx/job_manager_view.h" +#include "wx/drive_wipe_warning_dialog.h" +#include "lib/file_log.h" +#include "lib/dcpomatic_log.h" +#include "lib/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/disk_writer_messages.h" +#include <wx/wx.h> +#include <boost/process.hpp> +#ifdef DCPOMATIC_WINDOWS +#include <boost/process/windows.hpp> +#endif +#ifdef DCPOMATIC_OSX +#include <ApplicationServices/ApplicationServices.h> +#endif + +using std::string; +using std::exception; +using std::cout; +using std::cerr; +using std::runtime_error; +using boost::shared_ptr; + +class DOMFrame : public wxFrame +{ +public: + explicit DOMFrame (wxString const & title) + : wxFrame (0, -1, title) + , _nanomsg (true) + , _sizer (new wxBoxSizer(wxVERTICAL)) + { + /* 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); + s->Add (overall_panel, 1, wxEXPAND); + SetSizer (s); + + wxGridBagSizer* 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); + _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...")); + dcp_name_sizer->Add (_dcp_open, 0); + grid->Add (dcp_name_sizer, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND); + ++r; + + add_label_to_sizer (grid, overall_panel, _("Drive"), true, wxGBPosition(r, 0)); + wxBoxSizer* 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")); + drive_sizer->Add (_drive_refresh, 0); + grid->Add (drive_sizer, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND); + ++r; + + _jobs = new JobManagerView (overall_panel, false); + grid->Add (_jobs, wxGBPosition(r, 0), wxGBSpan(6, 2), wxEXPAND); + r += 6; + + _copy = new wxButton (overall_panel, wxID_ANY, _("Copy DCP")); + grid->Add (_copy, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND); + ++r; + + grid->AddGrowableCol (1); + + _dcp_open->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::open, this)); + _copy->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::copy, this)); + _drive->Bind (wxEVT_CHOICE, boost::bind(&DOMFrame::setup_sensitivity, this)); + _drive_refresh->Bind (wxEVT_BUTTON, boost::bind(&DOMFrame::drive_refresh, this)); + + _sizer->Add (grid, 1, wxALL | wxEXPAND, DCPOMATIC_DIALOG_BORDER); + overall_panel->SetSizer (_sizer); + Fit (); + SetSize (768, 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. + */ + dcpomatic_log.reset(new FileLog(config_path() / "disk.log")); + dcpomatic_log->set_types (dcpomatic_log->types() | LogEntry::TYPE_DISK); + LOG_DISK_NC("dcpomatic_disk started"); + + drive_refresh (); + + Bind (wxEVT_SIZE, boost::bind (&DOMFrame::sized, this, _1)); + + JobManager::instance()->ActiveJobsChanged.connect(boost::bind(&DOMFrame::setup_sensitivity, this)); + +#ifdef DCPOMATIC_WINDOWS + /* We must use ::shell here, it seems, to avoid error code 740 (related to privilege escalation) */ + LOG_DISK("Starting writer process %1", disk_writer_path().string()); + _writer = new boost::process::child (disk_writer_path(), boost::process::shell, boost::process::windows::hide); +#endif + +#ifdef DCPOMATIC_LINUX + LOG_DISK("Starting writer process %1", disk_writer_path().string()); + _writer = new boost::process::child (disk_writer_path()); +#endif + + /* _writer is always running on macOS at the moment */ + } + + ~DOMFrame () + { + _nanomsg.blocking_send(DISK_WRITER_QUIT "\n"); + } + +private: + void sized (wxSizeEvent& ev) + { + _sizer->Layout (); + ev.Skip (); + } + + void open () + { + wxDirDialog* 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 (); + + if (r != wxID_OK) { + return; + } + + _dcp_path = path; + _dcp_name->SetLabel (std_to_wx(_dcp_path->filename().string())); + setup_sensitivity (); + } + + void copy () + { + DCPOMATIC_ASSERT (_drive->GetSelection() != wxNOT_FOUND); + DCPOMATIC_ASSERT (static_cast<bool>(_dcp_path)); + DriveWipeWarningDialog* d = new DriveWipeWarningDialog (this, _drive->GetString(_drive->GetSelection())); + int const r = d->ShowModal (); + bool ok = r == wxID_OK && d->confirmed(); + d->Destroy (); + + if (!ok) { + return; + } + + JobManager::instance()->add(shared_ptr<Job>(new CopyToDriveJob(*_dcp_path, _drives[_drive->GetSelection()], _nanomsg))); + setup_sensitivity (); + } + + void drive_refresh () + { + int const sel = _drive->GetSelection (); + wxString current; + if (sel != wxNOT_FOUND) { + current = _drive->GetString (sel); + } + _drive->Clear (); + int re_select = wxNOT_FOUND; + int j = 0; + _drives.clear (); + BOOST_FOREACH (Drive i, get_drives()) { + if (!i.mounted()) { + _drives.push_back (i); + } + } + BOOST_FOREACH (Drive i, _drives) { + wxString const s = std_to_wx(i.description()); + if (s == current) { + re_select = j; + } + _drive->Append(s); + ++j; + } + _drive->SetSelection (re_select); + setup_sensitivity (); + } + + void setup_sensitivity () + { + _copy->Enable (static_cast<bool>(_dcp_path) && _drive->GetSelection() != wxNOT_FOUND && !JobManager::instance()->work_to_do()); + } + + wxStaticText* _dcp_name; + wxButton* _dcp_open; + wxChoice* _drive; + wxButton* _drive_refresh; + wxButton* _copy; + JobManagerView* _jobs; + boost::optional<boost::filesystem::path> _dcp_path; + std::vector<Drive> _drives; + boost::process::child* _writer; + Nanomsg _nanomsg; + wxSizer* _sizer; +}; + +class App : public wxApp +{ +public: + App () + : _frame (0) + {} + + bool OnInit () + { + try { + Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this)); + Config::Warning.connect (boost::bind (&App::config_warning, this, _1)); + + SetAppName (_("DCP-o-matic Disk Writer")); + + if (!wxApp::OnInit()) { + return false; + } + +#ifdef DCPOMATIC_LINUX + unsetenv ("UBUNTU_MENUPROXY"); +#endif + +#ifdef __WXOSX__ + ProcessSerialNumber serial; + GetCurrentProcess (&serial); + TransformProcessType (&serial, kProcessTransformToForegroundApplication); +#endif + + dcpomatic_setup_path_encoding (); + + /* Enable i18n; this will create a Config object + to look for a force-configured language. This Config + object will be wrong, however, because dcpomatic_setup + hasn't yet been called and there aren't any filters etc. + set up yet. + */ + dcpomatic_setup_i18n (); + + /* Set things up, including filters etc. + which will now be internationalised correctly. + */ + dcpomatic_setup (); + + /* Force the configuration to be re-loaded correctly next + time it is needed. + */ + Config::drop (); + + _frame = new DOMFrame (_("DCP-o-matic Disk Writer")); + SetTopWindow (_frame); + + _frame->Show (); + + signal_manager = new wxSignalManager (this); + Bind (wxEVT_IDLE, boost::bind (&App::idle, this, _1)); + } + catch (exception& e) + { + error_dialog (0, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what())); + } + + 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.")); + } + + void config_warning (string m) + { + message_dialog (_frame, std_to_wx(m)); + } + + void idle (wxIdleEvent& ev) + { + signal_manager->ui_idle (); + ev.Skip (); + } + + DOMFrame* _frame; +}; + +IMPLEMENT_APP (App) diff --git a/src/tools/dcpomatic_disk_writer.cc b/src/tools/dcpomatic_disk_writer.cc new file mode 100644 index 000000000..0bd5dcbc4 --- /dev/null +++ b/src/tools/dcpomatic_disk_writer.cc @@ -0,0 +1,474 @@ +/* + Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net> + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "lib/disk_writer_messages.h" +#include "lib/compose.hpp" +#include "lib/exceptions.h" +#include "lib/cross.h" +#include "lib/digester.h" +#include "lib/file_log.h" +#include "lib/dcpomatic_log.h" +#include "lib/nanomsg.h" +extern "C" { +#include <lwext4/ext4_mbr.h> +#include <lwext4/ext4_fs.h> +#include <lwext4/ext4_mkfs.h> +#include <lwext4/ext4_errno.h> +#include <lwext4/ext4_debug.h> +#include <lwext4/ext4.h> +} + +#ifdef DCPOMATIC_POSIX +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#endif + +#ifdef DCPOMATIC_OSX +#undef nil +extern "C" { +#include <lwext4/file_dev.h> +} +#endif + +#ifdef DCPOMATIC_LINUX +#include <linux/fs.h> +#include <polkit/polkit.h> +extern "C" { +#include <lwext4/file_dev.h> +} +#include <poll.h> +#endif + +#ifdef DCPOMATIC_WINDOWS +extern "C" { +#include <lwext4/file_windows.h> +} +#endif + +#include <glibmm.h> +#include <unistd.h> +#include <sys/types.h> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string.hpp> +#include <iostream> + +using std::cin; +using std::min; +using std::string; +using std::runtime_error; +using std::exception; +using boost::optional; + +#ifdef DCPOMATIC_LINUX +static PolkitAuthority* polkit_authority = 0; +#endif +static boost::filesystem::path dcp_path; +static std::string device; +static uint64_t const block_size = 4096; +static Nanomsg* nanomsg = 0; + +static +void +count (boost::filesystem::path dir, uint64_t& total_bytes) +{ + using namespace boost::filesystem; + for (directory_iterator i = directory_iterator(dir); i != directory_iterator(); ++i) { + if (is_directory(*i)) { + count (*i, total_bytes); + } else { + total_bytes += file_size (*i); + } + } +} + +static +string +write (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total) +{ + ext4_file out; + int r = ext4_fopen(&out, to.generic_string().c_str(), "wb"); + if (r != EOK) { + throw CopyError (String::compose("Failed to open file %1", to.generic_string()), r); + } + + FILE* in = fopen_boost (from, "rb"); + if (!in) { + ext4_fclose (&out); + throw CopyError (String::compose("Failed to open file %1", from.string()), 0); + } + + uint8_t* buffer = new uint8_t[block_size]; + Digester digester; + + uint64_t remaining = file_size (from); + while (remaining > 0) { + uint64_t const this_time = min(remaining, block_size); + size_t read = fread (buffer, 1, this_time, in); + if (read != this_time) { + fclose (in); + ext4_fclose (&out); + delete[] buffer; + throw CopyError (String::compose("Short read; expected %1 but read %2", this_time, read), 0); + } + + digester.add (buffer, this_time); + + size_t written; + r = ext4_fwrite (&out, buffer, this_time, &written); + if (r != EOK) { + fclose (in); + ext4_fclose (&out); + delete[] buffer; + throw CopyError ("Write failed", r); + } + if (written != this_time) { + fclose (in); + ext4_fclose (&out); + delete[] buffer; + throw CopyError (String::compose("Short write; expected %1 but wrote %2", this_time, written), 0); + } + remaining -= this_time; + total_remaining -= this_time; + nanomsg->blocking_send(String::compose(DISK_WRITER_PROGRESS "\n%1\n", (1 - float(total_remaining) / total))); + } + + fclose (in); + ext4_fclose (&out); + delete[] buffer; + + return digester.get (); +} + +static +string +read (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total) +{ + ext4_file in; + LOG_DISK("Opening %1 for read", to.generic_string()); + int r = ext4_fopen(&in, to.generic_string().c_str(), "rb"); + if (r != EOK) { + throw VerifyError (String::compose("Failed to open file %1", to.generic_string()), r); + } + LOG_DISK("Opened %1 for read", to.generic_string()); + + uint8_t* buffer = new uint8_t[block_size]; + Digester digester; + + uint64_t remaining = file_size (from); + while (remaining > 0) { + uint64_t const this_time = min(remaining, block_size); + size_t read; + r = ext4_fread (&in, buffer, this_time, &read); + if (read != this_time) { + ext4_fclose (&in); + delete[] buffer; + throw VerifyError (String::compose("Short read; expected %1 but read %2", this_time, read), 0); + } + + digester.add (buffer, this_time); + remaining -= this_time; + total_remaining -= this_time; + nanomsg->blocking_send(String::compose(DISK_WRITER_PROGRESS "\n%1\n", (1 - float(total_remaining) / total))); + } + + ext4_fclose (&in); + delete[] buffer; + + return digester.get (); +} + + +/** @param from File to copy from. + * @param to Directory to copy to. + */ +static +void +copy (boost::filesystem::path from, boost::filesystem::path to, uint64_t& total_remaining, uint64_t total) +{ + LOG_DISK ("Copy %1 -> %2", from.string(), to.generic_string()); + + using namespace boost::filesystem; + + path const cr = to / from.filename(); + + if (is_directory(from)) { + int r = ext4_dir_mk (cr.generic_string().c_str()); + if (r != EOK) { + throw CopyError (String::compose("Failed to create directory %1", cr.generic_string()), r); + } + + for (directory_iterator i = directory_iterator(from); i != directory_iterator(); ++i) { + copy (i->path(), cr, total_remaining, total); + } + } else { + string const write_digest = write (from, cr, total_remaining, total); + LOG_DISK ("Wrote %1 %2 with %3", from.string(), cr.generic_string(), write_digest); + string const read_digest = read (from, cr, total_remaining, total); + LOG_DISK ("Read %1 %2 with %3", from.string(), cr.generic_string(), write_digest); + if (write_digest != read_digest) { + throw VerifyError ("Hash of written data is incorrect", 0); + } + } +} + +static +void +write () +try +{ +// ext4_dmask_set (DEBUG_ALL); + + /* We rely on static initialization for these */ + static struct ext4_fs fs; + static struct ext4_mkfs_info info; + info.block_size = 1024; + info.inode_size = 128; + info.journal = false; + +#ifdef WIN32 + file_windows_name_set(device.c_str()); + struct ext4_blockdev* bd = file_windows_dev_get(); +#else + file_dev_name_set (device.c_str()); + struct ext4_blockdev* bd = file_dev_get (); +#endif + + if (!bd) { + throw CopyError ("Failed to open drive", 0); + } + LOG_DISK_NC ("Opened drive"); + + struct ext4_mbr_parts parts; + parts.division[0] = 100; + parts.division[1] = 0; + parts.division[2] = 0; + parts.division[3] = 0; + +#ifdef DCPOMATIC_LINUX + PrivilegeEscalator e; +#endif + + /* XXX: not sure if disk_id matters */ + int r = ext4_mbr_write (bd, &parts, 0); + + if (r) { + throw CopyError ("Failed to write MBR", r); + } + LOG_DISK_NC ("Wrote MBR"); + +#ifdef DCPOMATIC_WINDOWS + struct ext4_mbr_bdevs bdevs; + r = ext4_mbr_scan (bd, &bdevs); + if (r != EOK) { + throw CopyError ("Failed to read MBR", r); + } + + file_windows_partition_set (bdevs.partitions[0].part_offset, bdevs.partitions[0].part_size); +#endif + +#ifdef DCPOMATIC_LINUX + /* Re-read the partition table */ + int fd = open(device.c_str(), O_RDONLY); + ioctl(fd, BLKRRPART, NULL); + close(fd); +#endif + +#ifdef DCPOMATIC_LINUX + string partition = device; + /* XXX: don't know if this logic is sensible */ + if (partition.size() > 0 && isdigit(partition[partition.length() - 1])) { + partition += "p1"; + } else { + partition += "1"; + } + file_dev_name_set (partition.c_str()); + bd = file_dev_get (); +#endif + +#ifdef DCPOMATIC_OSX + string partition = device + "s1"; + file_dev_name_set (partition.c_str()); + bd = file_dev_get (); +#endif + + if (!bd) { + throw CopyError ("Failed to open partition", 0); + } + LOG_DISK_NC ("Opened partition"); + + nanomsg->blocking_send(DISK_WRITER_FORMATTING "\n"); + + r = ext4_mkfs(&fs, bd, &info, F_SET_EXT4); + if (r != EOK) { + throw CopyError ("Failed to make filesystem", r); + } + LOG_DISK_NC ("Made filesystem"); + + r = ext4_device_register(bd, "ext4_fs"); + if (r != EOK) { + throw CopyError ("Failed to register device", r); + } + LOG_DISK_NC ("Registered device"); + + r = ext4_mount("ext4_fs", "/mp/", false); + if (r != EOK) { + throw CopyError ("Failed to mount device", r); + } + LOG_DISK_NC ("Mounted device"); + + uint64_t total_bytes = 0; + count (dcp_path, total_bytes); + + /* XXX: this is a hack. We are going to "treat" every byte twice; write it, and then verify it. Double the + * bytes totals so that progress works itself out (assuming write is the same speed as read). + */ + total_bytes *= 2; + copy (dcp_path, "/mp", total_bytes, total_bytes); + + r = ext4_umount("/mp/"); + if (r != EOK) { + throw CopyError ("Failed to unmount device", r); + } + + ext4_device_unregister("ext4_fs"); + nanomsg->blocking_send(DISK_WRITER_OK "\n"); +} catch (CopyError& e) { + LOG_DISK("CopyError: %1 %2", e.message(), e.number()); + nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number())); +} catch (VerifyError& e) { + LOG_DISK("VerifyError: %1 %2", e.message(), e.number()); + nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n%2\n", e.message(), e.number())); +} catch (exception& e) { + LOG_DISK("Exception: %1", e.what()); + nanomsg->blocking_send(String::compose(DISK_WRITER_ERROR "\n%1\n0\n", e.what())); +} + +#ifdef DCPOMATIC_LINUX +static +void +polkit_callback (GObject *, GAsyncResult* res, gpointer) +{ + PolkitAuthorizationResult* result = polkit_authority_check_authorization_finish (polkit_authority, res, 0); + if (result && polkit_authorization_result_get_is_authorized(result)) { + write (); + } + if (result) { + g_object_unref (result); + } +} +#endif + +bool +idle () +{ + using namespace boost::algorithm; + + optional<string> s = nanomsg->nonblocking_get (); + if (!s) { + return true; + } + + if (*s == "Q") { + exit (EXIT_SUCCESS); + } else if (*s == "W") { + dcp_path = nanomsg->blocking_get(); + device = nanomsg->blocking_get(); + + /* Do some basic sanity checks; this is a bit belt-and-braces but it can't hurt... */ + +#ifdef DCPOMATIC_OSX + if (!starts_with(device, "/dev/disk")) { + LOG_DISK ("Will not write to %1", device); + nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n"); + return true; + } +#endif +#ifdef DCPOMATIC_LINUX + if (!starts_with(device, "/dev/sd") && !starts_with(device, "/dev/hd")) { + LOG_DISK ("Will not write to %1", device); + nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n"); + return true; + } +#endif +#ifdef DCPOMATIC_WINDOWS + if (!starts_with(device, "\\\\.\\PHYSICALDRIVE")) { + LOG_DISK ("Will not write to %1", device); + nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n"); + return true; + } +#endif + + bool on_drive_list = false; + bool mounted = false; + for (auto const& i: get_drives()) { + if (i.internal_name() == device) { + on_drive_list = true; + mounted = i.mounted(); + } + } + + if (!on_drive_list) { + LOG_DISK ("Will not write to %1 as it's not recognised as a drive", device); + nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n"); + return true; + } + if (mounted) { + LOG_DISK ("Will not write to %1 as it's mounted", device); + nanomsg->blocking_send(DISK_WRITER_ERROR "\nRefusing to write to this drive\n1\n"); + return true; + } + + LOG_DISK ("Here we go writing %1 to %2", dcp_path, device); + +#ifdef DCPOMATIC_LINUX + polkit_authority = polkit_authority_get_sync (0, 0); + PolkitSubject* subject = polkit_unix_process_new (getppid()); + polkit_authority_check_authorization ( + polkit_authority, subject, "com.dcpomatic.write-drive", 0, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, 0, polkit_callback, 0 + ); +#else + write (); +#endif + } + + return true; +} + +int +main () +{ + /* 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. + */ + dcpomatic_log.reset(new FileLog(config_path() / "disk_writer.log", LogEntry::TYPE_DISK)); + LOG_DISK_NC("dcpomatic_disk_writer started"); + + try { + nanomsg = new Nanomsg (false); + } catch (runtime_error& e) { + LOG_DISK_NC("Could not set up nanomsg socket"); + exit (EXIT_FAILURE); + } + + Glib::RefPtr<Glib::MainLoop> ml = Glib::MainLoop::create (); + Glib::signal_timeout().connect(sigc::ptr_fun(&idle), 500); + ml->run (); +} diff --git a/src/tools/wscript b/src/tools/wscript index 8af9e7589..c7c953a31 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -32,8 +32,15 @@ def build(bld): uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB ' uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG ' + if bld.env.ENABLE_DISK: + if bld.env.TARGET_LINUX: + uselib += 'POLKIT ' + uselib += 'LWEXT4 NANOMSG ' + if bld.env.TARGET_WINDOWS: uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE WINSOCK2 OLE32 DSOUND WINMM KSUSER ' + if bld.env.TARGET_LINUX: + uselib += 'DL ' cli_tools = [] if bld.env.VARIANT == 'swaroop-theater': @@ -42,6 +49,8 @@ def build(bld): cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create', 'swaroop_dcpomatic_ecinema', 'swaroop_dcpomatic_uuid'] else: cli_tools = ['dcpomatic_cli', 'dcpomatic_server_cli', 'server_test', 'dcpomatic_kdm_cli', 'dcpomatic_create'] + if bld.env.ENABLE_DISK: + cli_tools.append('dcpomatic_disk_writer') for t in cli_tools: obj = bld(features='cxx cxxprogram') @@ -49,6 +58,10 @@ def build(bld): obj.includes = ['..'] obj.use = ['libdcpomatic2'] obj.source = '%s.cc' % t + if bld.env.TARGET_WINDOWS and t == 'dcpomatic_disk_writer': + obj.source += ' ../../platform/windows/%s.rc' % t + # Prevent a console window opening when we start dcpomatic2_disk_writer + bld.env.LINKFLAGS.append('-Wl,-subsystem,windows') obj.target = t.replace('dcpomatic', 'dcpomatic2').replace('swaroop_', '') if t == 'server_test': obj.install_path = None @@ -61,6 +74,8 @@ def build(bld): gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'swaroop_dcpomatic_playlist'] else: gui_tools = ['dcpomatic', 'dcpomatic_batch', 'dcpomatic_server', 'dcpomatic_kdm', 'dcpomatic_player', 'dcpomatic_playlist'] + if bld.env.ENABLE_DISK: + gui_tools.append('dcpomatic_disk') for t in gui_tools: obj = bld(features='cxx cxxprogram') |
