Switch "command-like" options --list-cinemas and --list-dkdm-cpls to actual commands.
[dcpomatic.git] / src / lib / kdm_cli.cc
index 3402fa71cf0cbc061bb5f5c6398e725af93af925..f63fd2a550d30275c1a3ed16e468ddf79efb12d2 100644 (file)
@@ -27,7 +27,7 @@
 #include "cinema.h"
 #include "config.h"
 #include "dkdm_wrapper.h"
-#include "emailer.h"
+#include "email.h"
 #include "exceptions.h"
 #include "film.h"
 #include "kdm_with_metadata.h"
@@ -35,6 +35,7 @@
 #include <dcp/certificate.h>
 #include <dcp/decrypted_kdm.h>
 #include <dcp/encrypted_kdm.h>
+#include <dcp/filesystem.h>
 #include <getopt.h>
 
 
@@ -56,13 +57,17 @@ using namespace dcpomatic;
 static void
 help (std::function<void (string)> out)
 {
-       out (String::compose("Syntax: %1 [OPTION] <FILM|CPL-ID|DKDM>", program_name));
+       out (String::compose("Syntax: %1 [OPTION] [COMMAND] <FILM|CPL-ID|DKDM>", program_name));
+       out ("Commands:");
+       out ("create          create KDMs; default if no other command is specified");
+       out ("list-cinemas    list known cinemas from DCP-o-matic settings");
+       out ("list-dkdm-cpls  list CPLs for which DCP-o-matic has DKDMs");
        out ("  -h, --help                               show this help");
        out ("  -o, --output <path>                      output file or directory");
        out ("  -K, --filename-format <format>           filename format for KDMs");
        out ("  -Z, --container-name-format <format>     filename format for ZIP containers");
-       out ("  -f, --valid-from <time>                  valid from time (in local time zone of the cinema) (e.g. \"2013-09-28 01:41:51\") or \"now\"");
-       out ("  -t, --valid-to <time>                    valid to time (in local time zone of the cinema) (e.g. \"2014-09-28 01:41:51\")");
+       out ("  -f, --valid-from <time>                  valid from time (e.g. \"2013-09-28T01:41:51+04:00\", \"2018-01-01T12:00:30\") or \"now\"");
+       out ("  -t, --valid-to <time>                    valid to time (e.g. \"2014-09-28T01:41:51\")");
        out ("  -d, --valid-duration <duration>          valid duration (e.g. \"1 day\", \"4 hours\", \"2 weeks\")");
        out ("  -F, --formulation <formulation>          modified-transitional-1, multiple-modified-transitional-1, dci-any or dci-specific [default modified-transitional-1]");
        out ("  -p, --disable-forensic-marking-picture   disable forensic marking of pictures essences");
@@ -72,11 +77,11 @@ help (std::function<void (string)> out)
        out ("  -v, --verbose                            be verbose");
        out ("  -c, --cinema <name|email>                cinema name (when using -C) or name/email (to filter cinemas)");
        out ("  -S, --screen <name>                      screen name (when using -C) or screen name (to filter screens when using -c)");
-       out ("  -C, --certificate <file>                 file containing projector certificate");
-       out ("  -T, --trusted-device <file>              file containing a trusted device's certificate");
+       out ("  -C, --projector-certificate <file>       file containing projector certificate");
+       out ("  -T, --trusted-device-certificate <file>  file containing a trusted device's certificate");
+       out ("      --decryption-key <file>              file containing the private key which can decrypt the given DKDM");
+       out ("                                           (DCP-o-matic's configured private key will be used otherwise)");
        out ("      --cinemas-file <file>                use the given file as a list of cinemas instead of the current configuration");
-       out ("      --list-cinemas                       list known cinemas from the DCP-o-matic settings");
-       out ("      --list-dkdm-cpls                     list CPLs for which DCP-o-matic has DKDMs");
        out ("");
        out ("CPL-ID must be the ID of a CPL that is mentioned in DCP-o-matic's DKDM list.");
        out ("");
@@ -98,17 +103,6 @@ public:
 };
 
 
-static boost::posix_time::ptime
-time_from_string (string t)
-{
-       if (t == "now") {
-               return boost::posix_time::second_clock::local_time ();
-       }
-
-       return boost::posix_time::time_from_string (t);
-}
-
-
 static boost::posix_time::time_duration
 duration_from_string (string d)
 {
@@ -210,8 +204,8 @@ from_film (
        boost::filesystem::path output,
        dcp::NameFormat container_name_format,
        dcp::NameFormat filename_format,
-       boost::posix_time::ptime valid_from,
-       boost::posix_time::ptime valid_to,
+       dcp::LocalTime valid_from,
+       dcp::LocalTime valid_to,
        dcp::Formulation formulation,
        bool disable_forensic_marking_picture,
        optional<int> disable_forensic_marking_audio,
@@ -255,14 +249,21 @@ from_film (
                        }
                }
 
-
-               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE) != period_checks.end()) {
+               if (find_if(
+                       period_checks.begin(),
+                       period_checks.end(),
+                       [](KDMCertificatePeriod const& p) { return p.overlap == KDMCertificateOverlap::KDM_OUTSIDE_CERTIFICATE; }
+                  ) != period_checks.end()) {
                        throw KDMCLIError(
                                "Some KDMs would have validity periods which are completely outside the recipient certificate periods.  Such KDMs are very unlikely to work, so will not be created."
                                );
                }
 
-               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE) != period_checks.end()) {
+               if (find_if(
+                       period_checks.begin(),
+                       period_checks.end(),
+                       [](KDMCertificatePeriod const& p) { return p.overlap == KDMCertificateOverlap::KDM_OVERLAPS_CERTIFICATE; }
+                  ) != period_checks.end()) {
                        out("For some of these KDMs the recipient certificate's validity period will not cover the whole of the KDM validity period.  This might cause problems with the KDMs.");
                }
 
@@ -353,8 +354,8 @@ from_dkdm (
        boost::filesystem::path output,
        dcp::NameFormat container_name_format,
        dcp::NameFormat filename_format,
-       boost::posix_time::ptime valid_from,
-       boost::posix_time::ptime valid_to,
+       dcp::LocalTime valid_from,
+       dcp::LocalTime valid_to,
        dcp::Formulation formulation,
        bool disable_forensic_marking_picture,
        optional<int> disable_forensic_marking_audio,
@@ -372,18 +373,12 @@ from_dkdm (
                                continue;
                        }
 
-                       int const offset_hour = i->cinema ? i->cinema->utc_offset_hour() : 0;
-                       int const offset_minute = i->cinema ? i->cinema->utc_offset_minute() : 0;
-
-                       dcp::LocalTime begin(valid_from, dcp::UTCOffset(offset_hour, offset_minute));
-                       dcp::LocalTime end(valid_to, dcp::UTCOffset(offset_hour, offset_minute));
-
                        auto const kdm = kdm_from_dkdm(
                                                        dkdm,
                                                        i->recipient.get(),
                                                        i->trusted_device_thumbprints(),
-                                                       begin,
-                                                       end,
+                                                       valid_from,
+                                                       valid_to,
                                                        formulation,
                                                        disable_forensic_marking_picture,
                                                        disable_forensic_marking_audio
@@ -393,8 +388,8 @@ from_dkdm (
                        name_values['c'] = i->cinema ? i->cinema->name : "";
                        name_values['s'] = i->name;
                        name_values['f'] = kdm.content_title_text();
-                       name_values['b'] = begin.date() + " " + begin.time_of_day(true, false);
-                       name_values['e'] = end.date() + " " + end.time_of_day(true, false);
+                       name_values['b'] = valid_from.date() + " " + valid_from.time_of_day(true, false);
+                       name_values['e'] = valid_to.date() + " " + valid_to.time_of_day(true, false);
                        name_values['i'] = kdm.cpl_id();
 
                        kdms.push_back(make_shared<KDMWithMetadata>(name_values, i->cinema.get(), i->cinema ? i->cinema->emails : vector<string>(), kdm));
@@ -431,24 +426,40 @@ dump_dkdm_group (shared_ptr<DKDMGroup> group, int indent, std::function<void (st
 }
 
 
+static
+dcp::LocalTime
+time_from_string(string time)
+{
+       if (time == "now") {
+               return {};
+       }
+
+       if (time.length() > 10 && time[10] == ' ') {
+               time[10] = 'T';
+       }
+
+       return dcp::LocalTime(time);
+}
+
+
 optional<string>
 kdm_cli (int argc, char* argv[], std::function<void (string)> out)
 try
 {
-       boost::filesystem::path output = boost::filesystem::current_path();
+       boost::filesystem::path output = dcp::filesystem::current_path();
        auto container_name_format = Config::instance()->kdm_container_name_format();
        auto filename_format = Config::instance()->kdm_filename_format();
        optional<string> cinema_name;
        shared_ptr<Cinema> cinema;
-       optional<boost::filesystem::path> certificate;
+       optional<boost::filesystem::path> projector_certificate;
+       optional<boost::filesystem::path> decryption_key;
        optional<string> screen;
        vector<shared_ptr<Screen>> screens;
        optional<dcp::EncryptedKDM> dkdm;
-       optional<boost::posix_time::ptime> valid_from;
-       optional<boost::posix_time::ptime> valid_to;
+       optional<dcp::LocalTime> valid_from;
+       optional<dcp::LocalTime> valid_to;
        bool zip = false;
-       bool list_cinemas = false;
-       bool list_dkdm_cpls = false;
+       string command = "create";
        optional<string> duration_string;
        bool verbose = false;
        dcp::Formulation formulation = dcp::Formulation::MODIFIED_TRANSITIONAL_1;
@@ -480,15 +491,14 @@ try
                        { "verbose", no_argument, 0, 'v' },
                        { "cinema", required_argument, 0, 'c' },
                        { "screen", required_argument, 0, 'S' },
-                       { "certificate", required_argument, 0, 'C' },
-                       { "trusted-device", required_argument, 0, 'T' },
-                       { "list-cinemas", no_argument, 0, 'B' },
-                       { "list-dkdm-cpls", no_argument, 0, 'D' },
+                       { "projector-certificate", required_argument, 0, 'C' },
+                       { "trusted-device-certificate", required_argument, 0, 'T' },
+                       { "decryption-key", required_argument, 0, 'G' },
                        { "cinemas-file", required_argument, 0, 'E' },
                        { 0, 0, 0, 0 }
                };
 
-               int c = getopt_long (argc, argv, "ho:K:Z:f:t:d:F:pae::zvc:S:C:T:BDE:", long_options, &option_index);
+               int c = getopt_long (argc, argv, "ho:K:Z:f:t:d:F:pae::zvc:S:C:T:E:G:", long_options, &option_index);
 
                if (c == -1) {
                        break;
@@ -508,10 +518,10 @@ try
                        container_name_format = dcp::NameFormat (optarg);
                        break;
                case 'f':
-                       valid_from = time_from_string (optarg);
+                       valid_from = time_from_string(optarg);
                        break;
                case 't':
-                       valid_to = time_from_string (optarg);
+                       valid_to = dcp::LocalTime(optarg);
                        break;
                case 'd':
                        duration_string = optarg;
@@ -555,7 +565,7 @@ try
                           (for lookup) and by creating a Cinema which the next Screen will be added to.
                        */
                        cinema_name = optarg;
-                       cinema = make_shared<Cinema>(optarg, vector<string>(), "", 0, 0);
+                       cinema = make_shared<Cinema>(optarg, vector<string>(), "");
                        break;
                case 'S':
                        /* Similarly, this could be the name of a new (temporary) screen or the name of a screen
@@ -564,7 +574,7 @@ try
                        screen = optarg;
                        break;
                case 'C':
-                       certificate = optarg;
+                       projector_certificate = optarg;
                        break;
                case 'T':
                        /* A trusted device ends up in the last screen we made */
@@ -572,11 +582,8 @@ try
                                screens.back()->trusted_devices.push_back(TrustedDevice(dcp::Certificate(dcp::file_to_string(optarg))));
                        }
                        break;
-               case 'B':
-                       list_cinemas = true;
-                       break;
-               case 'D':
-                       list_dkdm_cpls = true;
+               case 'G':
+                       decryption_key = optarg;
                        break;
                case 'E':
                        cinemas_file = optarg;
@@ -584,13 +591,33 @@ try
                }
        }
 
+       vector<string> commands = {
+               "create",
+               "list-cinemas",
+               "list-dkdm-cpls"
+       };
+
+       if (optind < argc - 1) {
+               /* Command with some KDM / CPL / whever specified afterwards */
+               command = argv[optind++];
+       } else if (optind < argc) {
+               /* Look for a valid command, hoping that it's not the name of the KDM / CPL / whatever */
+               if (std::find(commands.begin(), commands.end(), argv[optind]) != commands.end()) {
+                       command = argv[optind];
+               }
+       }
+
+       if (std::find(commands.begin(), commands.end(), command) == commands.end()) {
+               throw KDMCLIError(String::compose("Unrecognised command %1", command));
+       }
+
        if (cinemas_file) {
                Config::instance()->set_cinemas_file(*cinemas_file);
        }
 
-       if (certificate) {
+       if (projector_certificate) {
                /* Make a new screen and add it to the current cinema */
-               dcp::CertificateChain chain(dcp::file_to_string(*certificate));
+               dcp::CertificateChain chain(dcp::file_to_string(*projector_certificate));
                auto screen_to_add = std::make_shared<Screen>(screen.get_value_or(""), "", chain.leaf(), boost::none, vector<TrustedDevice>());
                if (cinema) {
                        cinema->add_screen(screen_to_add);
@@ -598,15 +625,15 @@ try
                screens.push_back(screen_to_add);
        }
 
-       if (list_cinemas) {
+       if (command == "list-cinemas") {
                auto cinemas = Config::instance()->cinemas ();
                for (auto i: cinemas) {
-                       out (String::compose("%1 (%2)", i->name, Emailer::address_list (i->emails)));
+                       out (String::compose("%1 (%2)", i->name, Email::address_list(i->emails)));
                }
                return {};
        }
 
-       if (list_dkdm_cpls) {
+       if (command == "list-dkdm-cpls") {
                dump_dkdm_group (Config::instance()->dkdms(), 0, out);
                return {};
        }
@@ -635,15 +662,16 @@ try
        }
 
        if (duration_string) {
-               valid_to = valid_from.get() + duration_from_string (*duration_string);
+               valid_to = valid_from.get();
+               valid_to->add(duration_from_string(*duration_string));
        }
 
        if (verbose) {
-               out (String::compose("Making KDMs valid from %1 to %2", boost::posix_time::to_simple_string(valid_from.get()), boost::posix_time::to_simple_string(valid_to.get())));
+               out(String::compose("Making KDMs valid from %1 to %2", valid_from->as_string(), valid_to->as_string()));
        }
 
        string const thing = argv[optind];
-       if (boost::filesystem::is_directory(thing) && boost::filesystem::is_regular_file(boost::filesystem::path(thing) / "metadata.xml")) {
+       if (dcp::filesystem::is_directory(thing) && dcp::filesystem::is_regular_file(boost::filesystem::path(thing) / "metadata.xml")) {
                from_film (
                        screens,
                        thing,
@@ -661,7 +689,7 @@ try
                        out
                        );
        } else {
-               if (boost::filesystem::is_regular_file(thing)) {
+               if (dcp::filesystem::is_regular_file(thing)) {
                        dkdm = dcp::EncryptedKDM (dcp::file_to_string (thing));
                } else {
                        dkdm = find_dkdm (thing);
@@ -671,9 +699,11 @@ try
                        throw KDMCLIError ("could not find film or CPL ID corresponding to " + thing);
                }
 
+               string const key = decryption_key ? dcp::file_to_string(*decryption_key) : Config::instance()->decryption_chain()->key().get();
+
                from_dkdm (
                        screens,
-                       dcp::DecryptedKDM (*dkdm, Config::instance()->decryption_chain()->key().get()),
+                       dcp::DecryptedKDM(*dkdm, key),
                        verbose,
                        output,
                        container_name_format,