Add PlaylistEditorRestrictedMenus option, similar to PlayerRestrictedMenus.
[dcpomatic.git] / src / lib / config.cc
index 2db50d687a26aa94f2890b739310d44b43460020..160ef7645b008d0326b6fb329e3196bc851d7c27 100644 (file)
@@ -25,7 +25,6 @@
 #include "config.h"
 #include "constants.h"
 #include "cross.h"
-#include "crypto.h"
 #include "dcp_content_type.h"
 #include "dkdm_recipient.h"
 #include "dkdm_wrapper.h"
@@ -33,6 +32,7 @@
 #include "filter.h"
 #include "log.h"
 #include "ratio.h"
+#include "unzipper.h"
 #include "zipper.h"
 #include <dcp/certificate_chain.h>
 #include <dcp/name_format.h>
@@ -106,7 +106,7 @@ Config::set_defaults ()
        _language = optional<string> ();
        _default_still_length = 10;
        _default_dcp_content_type = DCPContentType::from_isdcf_name ("FTR");
-       _default_dcp_audio_channels = 6;
+       _default_dcp_audio_channels = 8;
        _default_j2k_bandwidth = 150000000;
        _default_audio_delay = 0;
        _default_interop = false;
@@ -140,9 +140,9 @@ Config::set_defaults ()
        _dkdm_recipients_file = read_path ("dkdm_recipients.xml");
        _show_hints_before_make_dcp = true;
        _confirm_kdm_email = true;
-       _kdm_container_name_format = dcp::NameFormat ("KDM %f %c");
-       _kdm_filename_format = dcp::NameFormat ("KDM %f %c %s");
-       _dkdm_filename_format = dcp::NameFormat ("DKDM %f %c %s");
+       _kdm_container_name_format = dcp::NameFormat("KDM_%f_%c");
+       _kdm_filename_format = dcp::NameFormat("KDM_%f_%c_%s");
+       _dkdm_filename_format = dcp::NameFormat("DKDM_%f_%c_%s");
        _dcp_metadata_filename_format = dcp::NameFormat ("%t");
        _dcp_asset_filename_format = dcp::NameFormat ("%t");
        _jump_to_selected = true;
@@ -177,6 +177,8 @@ Config::set_defaults ()
        _gdc_username = optional<string>();
        _gdc_password = optional<string>();
        _player_mode = PLAYER_MODE_WINDOW;
+       _player_restricted_menus = false;
+       _playlist_editor_restricted_menus = false;
        _image_display = 0;
        _video_view_type = VIDEO_VIEW_SIMPLE;
        _respect_kdm_validity_periods = true;
@@ -188,9 +190,16 @@ Config::set_defaults ()
        _custom_languages.clear ();
        _initial_paths.clear();
        _initial_paths["AddFilesPath"] = boost::none;
+       _initial_paths["AddKDMPath"] = boost::none;
        _initial_paths["AddDKDMPath"] = boost::none;
        _initial_paths["SelectCertificatePath"] = boost::none;
        _initial_paths["AddCombinerInputPath"] = boost::none;
+       _initial_paths["ExportSubtitlesPath"] = boost::none;
+       _initial_paths["ExportVideoPath"] = boost::none;
+       _initial_paths["DebugLogPath"] = boost::none;
+       _initial_paths["CinemaDatabasePath"] = boost::none;
+       _initial_paths["ConfigFilePath"] = boost::none;
+       _initial_paths["Preferences"] = boost::none;
        _use_isdcf_name_by_default = true;
        _write_kdms_to_disk = true;
        _email_kdms = false;
@@ -198,6 +207,8 @@ Config::set_defaults ()
        _default_kdm_duration = RoughDuration(1, RoughDuration::Unit::WEEKS);
        _auto_crop_threshold = 0.1;
        _last_release_notes_version = boost::none;
+       _allow_smpte_bv20 = false;
+       _isdcf_name_part_length = 14;
 
        _allowed_dcp_frame_rates.clear ();
        _allowed_dcp_frame_rates.push_back (24);
@@ -211,6 +222,10 @@ Config::set_defaults ()
        set_notification_email_to_default ();
        set_cover_sheet_to_default ();
 
+#ifdef DCPOMATIC_GROK
+       _grok = boost::none;
+#endif
+
        _main_divider_sash_position = {};
        _main_content_divider_sash_position = {};
 
@@ -289,7 +304,7 @@ Config::read_config()
 try
 {
        cxml::Document f ("Config");
-       f.read_file (config_read_file());
+       f.read_file(dcp::filesystem::fix_long_path(config_read_file()));
 
        auto version = f.optional_number_child<int> ("Version");
        if (version && *version < _current_version) {
@@ -485,7 +500,7 @@ try
           of the nags.
        */
        for (auto i: f.node_children("Nagged")) {
-               auto const id = i->number_attribute<int>("Id");
+               auto const id = number_attribute<int>(i, "Id", "id");
                if (id >= 0 && id < NAG_COUNT) {
                        _nagged[id] = raw_convert<int>(i->content());
                }
@@ -499,6 +514,7 @@ try
                        case BAD_SIGNER_UTF8_STRINGS:
                        case BAD_SIGNER_INCONSISTENT:
                        case BAD_SIGNER_VALIDITY_TOO_LONG:
+                       case BAD_SIGNER_DN_QUALIFIER:
                                _signer_chain = create_certificate_chain ();
                                break;
                        case BAD_DECRYPTION_INCONSISTENT:
@@ -556,7 +572,7 @@ try
        _default_notify = f.optional_bool_child("DefaultNotify").get_value_or(false);
 
        for (auto i: f.node_children("Notification")) {
-               int const id = i->number_attribute<int>("Id");
+               int const id = number_attribute<int>(i, "Id", "id");
                if (id >= 0 && id < NOTIFICATION_COUNT) {
                        _notification[id] = raw_convert<int>(i->content());
                }
@@ -578,6 +594,9 @@ try
                _player_mode = PLAYER_MODE_DUAL;
        }
 
+       _player_restricted_menus = f.optional_bool_child("PlayerRestrictedMenus").get_value_or(false);
+       _playlist_editor_restricted_menus = f.optional_bool_child("PlaylistEditorRestrictedMenus").get_value_or(false);
+
        _image_display = f.optional_number_child<int>("ImageDisplay").get_value_or(0);
        auto vc = f.optional_string_child("VideoViewType");
        if (vc && *vc == "opengl") {
@@ -629,6 +648,15 @@ try
                }
        }
 
+       _allow_smpte_bv20 = f.optional_bool_child("AllowSMPTEBv20").get_value_or(false);
+       _isdcf_name_part_length = f.optional_number_child<int>("ISDCFNamePartLength").get_value_or(14);
+
+#ifdef DCPOMATIC_GROK
+       if (auto grok = f.optional_node_child("Grok")) {
+               _grok = Grok(grok);
+       }
+#endif
+
        _export.read(f.optional_node_child("Export"));
 }
 catch (...) {
@@ -649,10 +677,10 @@ catch (...) {
 void
 Config::read_cinemas()
 {
-       if (boost::filesystem::exists (_cinemas_file)) {
+       if (dcp::filesystem::exists(_cinemas_file)) {
                try {
                        cxml::Document f("Cinemas");
-                       f.read_file(_cinemas_file);
+                       f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
                        read_cinemas(f);
                } catch (...) {
                        backup();
@@ -666,10 +694,10 @@ Config::read_cinemas()
 void
 Config::read_dkdm_recipients()
 {
-       if (boost::filesystem::exists (_dkdm_recipients_file)) {
+       if (dcp::filesystem::exists(_dkdm_recipients_file)) {
                try {
                        cxml::Document f("DKDMRecipients");
-                       f.read_file(_dkdm_recipients_file);
+                       f.read_file(dcp::filesystem::fix_long_path(_dkdm_recipients_file));
                        read_dkdm_recipients(f);
                } catch (...) {
                        backup();
@@ -951,7 +979,7 @@ Config::write_config () const
        /* [XML] Nagged 1 if a particular nag screen has been shown and should not be shown again, otherwise 0. */
        for (int i = 0; i < NAG_COUNT; ++i) {
                xmlpp::Element* e = root->add_child ("Nagged");
-               e->set_attribute ("Id", raw_convert<string>(i));
+               e->set_attribute("id", raw_convert<string>(i));
                e->add_child_text (_nagged[i] ? "1" : "0");
        }
        /* [XML] PreviewSound 1 to use sound in the GUI preview and player, otherwise 0. */
@@ -1006,7 +1034,7 @@ Config::write_config () const
        /* [XML] Notification 1 if a notification type is enabled, otherwise 0. */
        for (int i = 0; i < NOTIFICATION_COUNT; ++i) {
                xmlpp::Element* e = root->add_child ("Notification");
-               e->set_attribute ("Id", raw_convert<string>(i));
+               e->set_attribute("id", raw_convert<string>(i));
                e->add_child_text (_notification[i] ? "1" : "0");
        }
 
@@ -1038,7 +1066,7 @@ Config::write_config () const
        }
 
        /* [XML] PlayerMode <code>window</code> for a single window, <code>full</code> for full-screen and <code>dual</code> for full screen playback
-          with controls on another monitor.
+          with separate (advanced) controls.
        */
        switch (_player_mode) {
        case PLAYER_MODE_WINDOW:
@@ -1052,6 +1080,14 @@ Config::write_config () const
                break;
        }
 
+       if (_player_restricted_menus) {
+               root->add_child("PlayerRestrictedMenus")->add_child_text("1");
+       }
+
+       if (_playlist_editor_restricted_menus) {
+               root->add_child("PlaylistEditorRestrictedMenus")->add_child_text("1");
+       }
+
        /* [XML] ImageDisplay Screen number to put image on in dual-screen player mode. */
        root->add_child("ImageDisplay")->add_child_text(raw_convert<string>(_image_display));
        switch (_video_view_type) {
@@ -1110,6 +1146,17 @@ Config::write_config () const
                _default_add_file_location == DefaultAddFileLocation::SAME_AS_LAST_TIME ? "last" : "project"
                );
 
+       /* [XML] AllowSMPTEBv20 1 to allow the user to choose SMPTE (Bv2.0 only) as a standard, otherwise 0 */
+       root->add_child("AllowSMPTEBv20")->add_child_text(_allow_smpte_bv20 ? "1" : "0");
+       /* [XML] ISDCFNamePartLength Maximum length of the "name" part of an ISDCF name, which should be 14 according to the standard */
+       root->add_child("ISDCFNamePartLength")->add_child_text(raw_convert<string>(_isdcf_name_part_length));
+
+#ifdef DCPOMATIC_GROK
+       if (_grok) {
+               _grok->as_xml(root->add_child("Grok"));
+       }
+#endif
+
        _export.write(root->add_child("Export"));
 
        auto target = config_write_file();
@@ -1123,8 +1170,8 @@ Config::write_config () const
                }
                f.checked_write(s.c_str(), s.bytes());
                f.close();
-               boost::filesystem::remove (target);
-               boost::filesystem::rename (tmp, target);
+               dcp::filesystem::remove(target);
+               dcp::filesystem::rename(tmp, target);
        } catch (xmlpp::exception& e) {
                string s = e.what ();
                trim (s);
@@ -1147,8 +1194,8 @@ write_file (string root_node, string node, string version, list<shared_ptr<T>> t
 
        try {
                doc.write_to_file_formatted (file.string() + ".tmp");
-               boost::filesystem::remove (file);
-               boost::filesystem::rename (file.string() + ".tmp", file);
+               dcp::filesystem::remove(file);
+               dcp::filesystem::rename(file.string() + ".tmp", file);
        } catch (xmlpp::exception& e) {
                string s = e.what ();
                trim (s);
@@ -1191,7 +1238,7 @@ Config::directory_or (optional<boost::filesystem::path> dir, boost::filesystem::
        }
 
        boost::system::error_code ec;
-       auto const e = boost::filesystem::exists (*dir, ec);
+       auto const e = dcp::filesystem::exists(*dir, ec);
        if (ec || !e) {
                return a;
        }
@@ -1203,7 +1250,7 @@ void
 Config::drop ()
 {
        delete _instance;
-       _instance = 0;
+       _instance = nullptr;
 }
 
 void
@@ -1286,7 +1333,7 @@ Config::add_to_player_history (boost::filesystem::path p)
        add_to_history_internal (_player_history, p);
 }
 
-/** Remove non-existant items from the player history */
+/** Remove non-existent items from the player history */
 void
 Config::clean_player_history ()
 {
@@ -1314,7 +1361,7 @@ Config::clean_history_internal (vector<boost::filesystem::path>& h)
        h.clear ();
        for (auto i: old) {
                try {
-                       if (boost::filesystem::is_directory(i)) {
+                       if (dcp::filesystem::is_directory(i)) {
                                h.push_back (i);
                        }
                } catch (...) {
@@ -1327,7 +1374,7 @@ Config::clean_history_internal (vector<boost::filesystem::path>& h)
 bool
 Config::have_existing (string file)
 {
-       return boost::filesystem::exists (read_path(file));
+       return dcp::filesystem::exists(read_path(file));
 }
 
 
@@ -1354,10 +1401,10 @@ Config::set_cinemas_file (boost::filesystem::path file)
 
        _cinemas_file = file;
 
-       if (boost::filesystem::exists (_cinemas_file)) {
+       if (dcp::filesystem::exists(_cinemas_file)) {
                /* Existing file; read it in */
                cxml::Document f ("Cinemas");
-               f.read_file (_cinemas_file);
+               f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
                read_cinemas (f);
        }
 
@@ -1386,12 +1433,12 @@ Config::save_template (shared_ptr<const Film> film, string name) const
 list<string>
 Config::templates () const
 {
-       if (!boost::filesystem::exists(read_path("templates"))) {
+       if (!dcp::filesystem::exists(read_path("templates"))) {
                return {};
        }
 
        list<string> n;
-       for (auto const& i: boost::filesystem::directory_iterator(read_path("templates"))) {
+       for (auto const& i: dcp::filesystem::directory_iterator(read_path("templates"))) {
                n.push_back (i.path().filename().string());
        }
        return n;
@@ -1400,7 +1447,7 @@ Config::templates () const
 bool
 Config::existing_template (string name) const
 {
-       return boost::filesystem::exists (template_read_path(name));
+       return dcp::filesystem::exists(template_read_path(name));
 }
 
 
@@ -1421,13 +1468,13 @@ Config::template_write_path (string name) const
 void
 Config::rename_template (string old_name, string new_name) const
 {
-       boost::filesystem::rename (template_read_path(old_name), template_write_path(new_name));
+       dcp::filesystem::rename(template_read_path(old_name), template_write_path(new_name));
 }
 
 void
 Config::delete_template (string name) const
 {
-       boost::filesystem::remove (template_write_path(name));
+       dcp::filesystem::remove(template_write_path(name));
 }
 
 /** @return Path to the config.xml containing the actual settings, following a link if required */
@@ -1435,14 +1482,14 @@ boost::filesystem::path
 config_file (boost::filesystem::path main)
 {
        cxml::Document f ("Config");
-       if (!boost::filesystem::exists (main)) {
+       if (!dcp::filesystem::exists(main)) {
                /* It doesn't exist, so there can't be any links; just return it */
                return main;
        }
 
        /* See if there's a link */
        try {
-               f.read_file (main);
+               f.read_file(dcp::filesystem::fix_long_path(main));
                auto link = f.optional_string_child("Link");
                if (link) {
                        return *link;
@@ -1496,7 +1543,7 @@ void
 Config::copy_and_link (boost::filesystem::path new_file) const
 {
        write ();
-       boost::filesystem::copy_file (config_read_file(), new_file, boost::filesystem::copy_option::overwrite_if_exists);
+       dcp::filesystem::copy_file(config_read_file(), new_file, boost::filesystem::copy_option::overwrite_if_exists);
        link (new_file);
 }
 
@@ -1580,6 +1627,9 @@ Config::check_certificates () const
                if ((i.not_after().year() - i.not_before().year()) > 15) {
                        bad = BAD_SIGNER_VALIDITY_TOO_LONG;
                }
+               if (dcp::escape_digest(i.subject_dn_qualifier()) != dcp::public_key_digest(i.public_key())) {
+                       bad = BAD_SIGNER_DN_QUALIFIER;
+               }
        }
 
        if (!_signer_chain->chain_valid() || !_signer_chain->private_key_valid()) {
@@ -1601,10 +1651,10 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
 
        auto config = Config::instance();
        zipper.add ("config.xml", dcp::file_to_string(config->config_read_file()));
-       if (boost::filesystem::exists(config->cinemas_file())) {
+       if (dcp::filesystem::exists(config->cinemas_file())) {
                zipper.add ("cinemas.xml", dcp::file_to_string(config->cinemas_file()));
        }
-       if (boost::filesystem::exists(config->dkdm_recipients_file())) {
+       if (dcp::filesystem::exists(config->dkdm_recipients_file())) {
                zipper.add ("dkdm_recipients.xml", dcp::file_to_string(config->dkdm_recipients_file()));
        }
 
@@ -1612,6 +1662,38 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
 }
 
 
+void
+Config::load_from_zip(boost::filesystem::path zip_file)
+{
+       Unzipper unzipper(zip_file);
+       dcp::write_string_to_file(unzipper.get("config.xml"), config_write_file());
+
+       try {
+               dcp::write_string_to_file(unzipper.get("cinemas.xml"), cinemas_file());
+               dcp::write_string_to_file(unzipper.get("dkdm_recipient.xml"), dkdm_recipients_file());
+       } catch (std::runtime_error&) {}
+
+       read();
+
+       changed(Property::USE_ANY_SERVERS);
+       changed(Property::SERVERS);
+       changed(Property::CINEMAS);
+       changed(Property::DKDM_RECIPIENTS);
+       changed(Property::SOUND);
+       changed(Property::SOUND_OUTPUT);
+       changed(Property::PLAYER_CONTENT_DIRECTORY);
+       changed(Property::PLAYER_PLAYLIST_DIRECTORY);
+       changed(Property::PLAYER_DEBUG_LOG);
+       changed(Property::HISTORY);
+       changed(Property::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS);
+       changed(Property::AUDIO_MAPPING);
+       changed(Property::AUTO_CROP_THRESHOLD);
+       changed(Property::ALLOW_SMPTE_BV20);
+       changed(Property::ISDCF_NAME_PART_LENGTH);
+       changed(Property::OTHER);
+}
+
+
 void
 Config::set_initial_path(string id, boost::filesystem::path path)
 {
@@ -1626,7 +1708,44 @@ optional<boost::filesystem::path>
 Config::initial_path(string id) const
 {
        auto iter = _initial_paths.find(id);
-       DCPOMATIC_ASSERT(iter != _initial_paths.end());
+       if (iter == _initial_paths.end()) {
+               return {};
+       }
        return iter->second;
 }
 
+
+#ifdef DCPOMATIC_GROK
+
+Config::Grok::Grok(cxml::ConstNodePtr node)
+       : enable(node->bool_child("Enable"))
+       , binary_location(node->string_child("BinaryLocation"))
+       , selected(node->number_child<int>("Selected"))
+       , licence_server(node->string_child("LicenceServer"))
+       , licence_port(node->number_child<int>("LicencePort"))
+       , licence(node->string_child("Licence"))
+{
+
+}
+
+
+void
+Config::Grok::as_xml(xmlpp::Element* node) const
+{
+       node->add_child("BinaryLocation")->add_child_text(binary_location.string());
+       node->add_child("Enable")->add_child_text((enable ? "1" : "0"));
+       node->add_child("Selected")->add_child_text(raw_convert<string>(selected));
+       node->add_child("LicenceServer")->add_child_text(licence_server);
+       node->add_child("LicencePort")->add_child_text(raw_convert<string>(licence_port));
+       node->add_child("Licence")->add_child_text(licence);
+}
+
+
+void
+Config::set_grok(Grok const& grok)
+{
+       _grok = grok;
+       changed(GROK);
+}
+
+#endif