summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2023-05-20 22:51:49 +0200
committerCarl Hetherington <cth@carlh.net>2024-05-06 20:42:50 +0200
commita3fcbb3a76e079a5485a0552ea5d35b8d6739116 (patch)
tree58f6476b7197c0e32b5aa3d52d0859a9b04db268 /src
parenta4105c6e8dc83407abc9b12e80c958673c942888 (diff)
Use sqlite for cinema and DKDM recipient lists.
Diffstat (limited to 'src')
-rw-r--r--src/lib/cinema.cc103
-rw-r--r--src/lib/cinema.h33
-rw-r--r--src/lib/cinema_list.cc466
-rw-r--r--src/lib/cinema_list.h126
-rw-r--r--src/lib/config.cc210
-rw-r--r--src/lib/config.h67
-rw-r--r--src/lib/dkdm_recipient.cc29
-rw-r--r--src/lib/dkdm_recipient.h6
-rw-r--r--src/lib/dkdm_recipient_list.cc243
-rw-r--r--src/lib/dkdm_recipient_list.h90
-rw-r--r--src/lib/exceptions.h55
-rw-r--r--src/lib/id.cc30
-rw-r--r--src/lib/id.h48
-rw-r--r--src/lib/kdm_cli.cc134
-rw-r--r--src/lib/kdm_with_metadata.h7
-rw-r--r--src/lib/screen.cc46
-rw-r--r--src/lib/screen.h10
-rw-r--r--src/lib/sqlite_statement.cc111
-rw-r--r--src/lib/sqlite_statement.h50
-rw-r--r--src/lib/sqlite_table.cc79
-rw-r--r--src/lib/sqlite_table.h54
-rw-r--r--src/lib/sqlite_transaction.cc50
-rw-r--r--src/lib/sqlite_transaction.h40
-rw-r--r--src/lib/unzipper.cc14
-rw-r--r--src/lib/unzipper.h3
-rw-r--r--src/lib/util.cc20
-rw-r--r--src/lib/util.h1
-rw-r--r--src/lib/wscript9
-rw-r--r--src/tools/dcpomatic.cc74
-rw-r--r--src/tools/dcpomatic_batch.cc36
-rw-r--r--src/tools/dcpomatic_kdm.cc6
-rw-r--r--src/tools/wscript2
-rw-r--r--src/wx/kdm_dialog.cc17
-rw-r--r--src/wx/load_config_from_zip_dialog.cc59
-rw-r--r--src/wx/load_config_from_zip_dialog.h41
-rw-r--r--src/wx/recipients_panel.cc65
-rw-r--r--src/wx/recipients_panel.h7
-rw-r--r--src/wx/screens_panel.cc282
-rw-r--r--src/wx/screens_panel.h50
-rw-r--r--src/wx/wscript1
-rw-r--r--src/wx/wx_util.cc14
41 files changed, 2059 insertions, 729 deletions
diff --git a/src/lib/cinema.cc b/src/lib/cinema.cc
deleted file mode 100644
index b1681fc28..000000000
--- a/src/lib/cinema.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- Copyright (C) 2013-2021 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 "cinema.h"
-#include "screen.h"
-#include "dcpomatic_assert.h"
-#include <libcxml/cxml.h>
-#include <dcp/raw_convert.h>
-#include <libxml++/libxml++.h>
-
-
-using std::make_shared;
-using std::shared_ptr;
-using std::string;
-using dcp::raw_convert;
-using dcpomatic::Screen;
-
-
-Cinema::Cinema (cxml::ConstNodePtr node)
- : name (node->string_child ("Name"))
- , notes (node->optional_string_child("Notes").get_value_or(""))
-{
- for (auto i: node->node_children("Email")) {
- emails.push_back (i->content ());
- }
-
- int hour = 0;
-
- if (node->optional_number_child<int>("UTCOffset")) {
- hour = node->number_child<int>("UTCOffset");
- } else {
- hour = node->optional_number_child<int>("UTCOffsetHour").get_value_or(0);
- }
-
- int minute = node->optional_number_child<int>("UTCOffsetMinute").get_value_or(0);
-
- utc_offset= { hour, minute };
-}
-
-/* This is necessary so that we can use shared_from_this in add_screen (which cannot be done from
- a constructor)
-*/
-void
-Cinema::read_screens (cxml::ConstNodePtr node)
-{
- for (auto i: node->node_children("Screen")) {
- add_screen (make_shared<Screen>(i));
- }
-}
-
-void
-Cinema::as_xml (xmlpp::Element* parent) const
-{
- cxml::add_text_child(parent, "Name", name);
-
- for (auto i: emails) {
- cxml::add_text_child(parent, "Email", i);
- }
-
- cxml::add_text_child(parent, "Notes", notes);
-
- cxml::add_text_child(parent, "UTCOffsetHour", raw_convert<string>(utc_offset.hour()));
- cxml::add_text_child(parent, "UTCOffsetMinute", raw_convert<string>(utc_offset.minute()));
-
- for (auto i: _screens) {
- i->as_xml(cxml::add_child(parent, "Screen"));
- }
-}
-
-void
-Cinema::add_screen (shared_ptr<Screen> s)
-{
- s->cinema = shared_from_this ();
- _screens.push_back (s);
-}
-
-void
-Cinema::remove_screen (shared_ptr<Screen> s)
-{
- auto iter = std::find(_screens.begin(), _screens.end(), s);
- if (iter != _screens.end()) {
- _screens.erase(iter);
- }
-}
-
diff --git a/src/lib/cinema.h b/src/lib/cinema.h
index 05f6fb7fc..44f232f91 100644
--- a/src/lib/cinema.h
+++ b/src/lib/cinema.h
@@ -18,31 +18,24 @@
*/
+
/** @file src/lib/cinema.h
* @brief Cinema class.
*/
#include <dcp/utc_offset.h>
-#include <libcxml/cxml.h>
#include <memory>
+#include <string>
+#include <vector>
-namespace xmlpp {
- class Element;
-}
-
-namespace dcpomatic {
- class Screen;
-}
-
/** @class Cinema
* @brief A description of a Cinema for KDM generation.
*
- * This is a cinema name, some metadata and a list of
- * Screen objects.
+ * This is a cinema name and some metadata.
*/
-class Cinema : public std::enable_shared_from_this<Cinema>
+class Cinema
{
public:
Cinema(std::string const & name_, std::vector<std::string> const & e, std::string notes_, dcp::UTCOffset utc_offset_)
@@ -52,24 +45,8 @@ public:
, utc_offset(std::move(utc_offset_))
{}
- explicit Cinema (cxml::ConstNodePtr);
-
- void read_screens (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const;
-
- void add_screen (std::shared_ptr<dcpomatic::Screen>);
- void remove_screen (std::shared_ptr<dcpomatic::Screen>);
-
std::string name;
std::vector<std::string> emails;
std::string notes;
dcp::UTCOffset utc_offset;
-
- std::vector<std::shared_ptr<dcpomatic::Screen>> screens() const {
- return _screens;
- }
-
-private:
- std::vector<std::shared_ptr<dcpomatic::Screen>> _screens;
};
diff --git a/src/lib/cinema_list.cc b/src/lib/cinema_list.cc
new file mode 100644
index 000000000..41b9dbab3
--- /dev/null
+++ b/src/lib/cinema_list.cc
@@ -0,0 +1,466 @@
+/*
+ Copyright (C) 2023 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 "cinema.h"
+#include "cinema_list.h"
+#include "config.h"
+#include "dcpomatic_assert.h"
+#include "exceptions.h"
+#include "screen.h"
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+#include "util.h"
+#include <dcp/certificate.h>
+#include <sqlite3.h>
+#include <boost/algorithm/string.hpp>
+#include <iostream>
+#include <numeric>
+
+
+using std::pair;
+using std::make_pair;
+using std::string;
+using std::vector;
+using boost::optional;
+
+
+CinemaList::CinemaList()
+ : _cinemas("cinemas")
+ , _screens("screens")
+ , _trusted_devices("trusted_devices")
+{
+ setup_tables();
+ setup(Config::instance()->cinemas_file());
+}
+
+
+CinemaList::CinemaList(boost::filesystem::path db_file)
+ : _cinemas("cinemas")
+ , _screens("screens")
+ , _trusted_devices("trusted_devices")
+{
+ setup_tables();
+ setup(db_file);
+}
+
+
+void
+CinemaList::setup_tables()
+{
+ _cinemas.add_column("name", "TEXT");
+ _cinemas.add_column("emails", "TEXT");
+ _cinemas.add_column("notes", "TEXT");
+ _cinemas.add_column("utc_offset_hour", "INTEGER");
+ _cinemas.add_column("utc_offset_minute", "INTEGER");
+
+ _screens.add_column("cinema", "INTEGER");
+ _screens.add_column("name", "TEXT");
+ _screens.add_column("notes", "TEXT");
+ _screens.add_column("recipient", "TEXT");
+ _screens.add_column("recipient_file", "TEXT");
+
+ _trusted_devices.add_column("screen", "INTEGER");
+ _trusted_devices.add_column("certificate_or_thumbprint", "TEXT");
+}
+
+
+void
+CinemaList::read_legacy_file(boost::filesystem::path xml_file)
+{
+ cxml::Document doc("Cinemas");
+ doc.read_file(xml_file);
+ read_legacy_document(doc);
+}
+
+
+void
+CinemaList::read_legacy_string(std::string const& xml)
+{
+ cxml::Document doc("Cinemas");
+ doc.read_string(xml);
+ read_legacy_document(doc);
+}
+
+
+void
+CinemaList::read_legacy_document(cxml::Document const& doc)
+{
+ for (auto cinema_node: doc.node_children("Cinema")) {
+ vector<string> emails;
+ for (auto email_node: cinema_node->node_children("Email")) {
+ emails.push_back(email_node->content());
+ }
+
+ int hour = 0;
+ if (cinema_node->optional_number_child<int>("UTCOffset")) {
+ hour = cinema_node->number_child<int>("UTCOffset");
+ } else {
+ hour = cinema_node->optional_number_child<int>("UTCOffsetHour").get_value_or(0);
+ }
+
+ int minute = cinema_node->optional_number_child<int>("UTCOffsetMinute").get_value_or(0);
+
+ Cinema cinema(
+ cinema_node->string_child("Name"),
+ emails,
+ cinema_node->string_child("Notes"),
+ dcp::UTCOffset(hour, minute)
+ );
+
+ auto cinema_id = add_cinema(cinema);
+
+ for (auto screen_node: cinema_node->node_children("Screen")) {
+ optional<dcp::Certificate> recipient;
+ if (auto recipient_string = screen_node->optional_string_child("Recipient")) {
+ recipient = dcp::Certificate(*recipient_string);
+ }
+ vector<TrustedDevice> trusted_devices;
+ for (auto trusted_device_node: screen_node->node_children("TrustedDevice")) {
+ trusted_devices.push_back(TrustedDevice(trusted_device_node->content()));
+ }
+ dcpomatic::Screen screen(
+ screen_node->string_child("Name"),
+ screen_node->string_child("Notes"),
+ recipient,
+ screen_node->optional_string_child("RecipientFile"),
+ trusted_devices
+ );
+ add_screen(cinema_id, screen);
+ }
+ }
+}
+
+
+void
+CinemaList::clear()
+{
+ for (auto table: { "cinemas", "screens", "trusted_devices" }) {
+ SQLiteStatement sql(_db, String::compose("DELETE FROM %1", table));
+ sql.execute();
+ }
+}
+
+
+void
+CinemaList::setup(boost::filesystem::path db_file)
+{
+#ifdef DCPOMATIC_WINDOWS
+ auto rc = sqlite3_open16(db_file.c_str(), &_db);
+#else
+ auto rc = sqlite3_open(db_file.c_str(), &_db);
+#endif
+ if (rc != SQLITE_OK) {
+ throw FileError("Could not open SQLite database", db_file);
+ }
+
+ sqlite3_busy_timeout(_db, 500);
+
+ SQLiteStatement cinemas(_db, _cinemas.create());
+ cinemas.execute();
+
+ SQLiteStatement screens(_db, _screens.create());
+ screens.execute();
+
+ SQLiteStatement devices(_db, _trusted_devices.create());
+ devices.execute();
+}
+
+
+CinemaList::CinemaList(CinemaList&& other)
+ : _db(other._db)
+ , _cinemas(std::move(other._cinemas))
+ , _screens(std::move(other._screens))
+ , _trusted_devices(std::move(other._trusted_devices))
+{
+ other._db = nullptr;
+}
+
+
+CinemaList&
+CinemaList::operator=(CinemaList&& other)
+{
+ if (this != &other) {
+ _db = other._db;
+ other._db = nullptr;
+ }
+ return *this;
+}
+
+
+CinemaID
+CinemaList::add_cinema(Cinema const& cinema)
+{
+ SQLiteStatement statement(_db, _cinemas.insert());
+
+ statement.bind_text(1, cinema.name);
+ statement.bind_text(2, join_strings(cinema.emails));
+ statement.bind_text(3, cinema.notes);
+ statement.bind_int64(4, cinema.utc_offset.hour());
+ statement.bind_int64(5, cinema.utc_offset.minute());
+
+ statement.execute();
+
+ return sqlite3_last_insert_rowid(_db);
+}
+
+
+void
+CinemaList::update_cinema(CinemaID id, Cinema const& cinema)
+{
+ SQLiteStatement statement(_db, _cinemas.update("WHERE id=?"));
+
+ statement.bind_text(1, cinema.name);
+ statement.bind_text(2, join_strings(cinema.emails));
+ statement.bind_text(3, cinema.notes);
+ statement.bind_int64(4, cinema.utc_offset.hour());
+ statement.bind_int64(5, cinema.utc_offset.minute());
+ statement.bind_int64(6, id.get());
+
+ statement.execute();
+}
+
+
+void
+CinemaList::remove_cinema(CinemaID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM cinemas WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+CinemaList::~CinemaList()
+{
+ if (_db) {
+ sqlite3_close(_db);
+ }
+}
+
+
+static
+vector<pair<CinemaID, Cinema>>
+cinemas_from_result(SQLiteStatement& statement)
+{
+ vector<pair<CinemaID, Cinema>> output;
+
+ statement.execute([&output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ CinemaID const id = statement.column_int64(0);
+ auto const name = statement.column_text(1);
+ auto const join_strings = statement.column_text(2);
+ vector<string> emails;
+ boost::algorithm::split(emails, join_strings, boost::is_any_of(" "));
+ auto const notes = statement.column_text(3);
+ auto const utc_offset_hour = static_cast<int>(statement.column_int64(4));
+ auto const utc_offset_minute = static_cast<int>(statement.column_int64(5));
+ output.push_back(make_pair(id, Cinema(name, { emails }, notes, dcp::UTCOffset{utc_offset_hour, utc_offset_minute})));
+ });
+
+ return output;
+}
+
+
+vector<pair<CinemaID, Cinema>>
+CinemaList::cinemas() const
+{
+ SQLiteStatement statement(_db, _cinemas.select("ORDER BY name ASC"));
+ return cinemas_from_result(statement);
+}
+
+
+optional<Cinema>
+CinemaList::cinema(CinemaID id) const
+{
+ SQLiteStatement statement(_db, _cinemas.select("WHERE id=?"));
+ statement.bind_int64(1, id.get());
+ auto result = cinemas_from_result(statement);
+ if (result.empty()) {
+ return {};
+ }
+ return result[0].second;
+}
+
+ScreenID
+CinemaList::add_screen(CinemaID cinema_id, dcpomatic::Screen const& screen)
+{
+ SQLiteTransaction transaction(_db);
+
+ SQLiteStatement add_screen(_db, _screens.insert());
+
+ add_screen.bind_int64(1, cinema_id.get());
+ add_screen.bind_text(2, screen.name);
+ add_screen.bind_text(3, screen.notes);
+ add_screen.bind_text(4, screen.recipient->certificate(true));
+ add_screen.bind_text(5, screen.recipient_file.get_value_or(""));
+
+ add_screen.execute();
+
+ auto const screen_id = sqlite3_last_insert_rowid(_db);
+
+ for (auto device: screen.trusted_devices) {
+ SQLiteStatement add_device(_db, _trusted_devices.insert());
+ add_device.bind_int64(1, screen_id);
+ add_device.bind_text(2, device.as_string());
+ }
+
+ transaction.commit();
+
+ return screen_id;
+}
+
+
+dcpomatic::Screen
+CinemaList::screen_from_result(SQLiteStatement& statement, ScreenID screen_id) const
+{
+ auto certificate_string = statement.column_text(4);
+ optional<dcp::Certificate> certificate = certificate_string.empty() ? optional<dcp::Certificate>() : dcp::Certificate(certificate_string);
+ auto recipient_file_string = statement.column_text(5);
+ optional<string> recipient_file = recipient_file_string.empty() ? optional<string>() : recipient_file_string;
+
+ SQLiteStatement trusted_devices_statement(_db, _trusted_devices.select("WHERE screen=?"));
+ trusted_devices_statement.bind_int64(1, screen_id.get());
+ vector<TrustedDevice> trusted_devices;
+ trusted_devices_statement.execute([&trusted_devices](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 1);
+ auto description = statement.column_text(1);
+ if (boost::algorithm::starts_with(description, "-----BEGIN CERTIFICATE")) {
+ trusted_devices.push_back(TrustedDevice(dcp::Certificate(description)));
+ } else {
+ trusted_devices.push_back(TrustedDevice(description));
+ }
+ });
+
+ return dcpomatic::Screen(statement.column_text(2), statement.column_text(3), certificate, recipient_file, trusted_devices);
+}
+
+
+optional<dcpomatic::Screen>
+CinemaList::screen(ScreenID screen_id) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE id=?"));
+ statement.bind_int64(1, screen_id.get());
+
+ optional<dcpomatic::Screen> output;
+
+ statement.execute([this, &output, screen_id](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ output = screen_from_result(statement, screen_id);
+ });
+
+ return output;
+}
+
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens_from_result(SQLiteStatement& statement) const
+{
+ vector<pair<ScreenID, dcpomatic::Screen>> output;
+
+ statement.execute([this, &output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 6);
+ ScreenID const screen_id = statement.column_int64(0);
+ output.push_back({screen_id, screen_from_result(statement, screen_id)});
+ });
+
+ return output;
+}
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens(CinemaID cinema_id) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE cinema=?"));
+ statement.bind_int64(1, cinema_id.get());
+ return screens_from_result(statement);
+}
+
+
+vector<pair<ScreenID, dcpomatic::Screen>>
+CinemaList::screens_by_cinema_and_name(CinemaID id, std::string const& name) const
+{
+ SQLiteStatement statement(_db, _screens.select("WHERE cinema=? AND name=?"));
+ statement.bind_int64(1, id.get());
+ statement.bind_text(2, name);
+ return screens_from_result(statement);
+}
+
+
+optional<std::pair<CinemaID, Cinema>>
+CinemaList::cinema_by_name_or_email(std::string const& text) const
+{
+ SQLiteStatement statement(_db, _cinemas.select("WHERE name LIKE ? OR EMAILS LIKE ?"));
+ auto const wildcard = string("%") + text + "%";
+ statement.bind_text(1, wildcard);
+ statement.bind_text(2, wildcard);
+
+ auto all = cinemas_from_result(statement);
+ if (all.empty()) {
+ return {};
+ }
+ return all[0];
+}
+
+
+void
+CinemaList::update_screen(ScreenID id, dcpomatic::Screen const& screen)
+{
+ SQLiteStatement statement(_db, _screens.update("WHERE id=?"));
+
+ statement.bind_text(1, screen.name);
+ statement.bind_text(2, screen.notes);
+ statement.bind_text(3, screen.recipient->certificate(true));
+ statement.bind_text(4, screen.recipient_file.get_value_or(""));
+ statement.bind_int64(5, id.get());
+
+ statement.execute();
+}
+
+
+void
+CinemaList::remove_screen(ScreenID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM screens WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+optional<dcp::UTCOffset>
+CinemaList::unique_utc_offset(std::set<CinemaID> const& cinemas_to_check)
+{
+ optional<dcp::UTCOffset> offset;
+
+ for (auto const& cinema: cinemas()) {
+ if (cinemas_to_check.find(cinema.first) == cinemas_to_check.end()) {
+ continue;
+ }
+
+ if (!offset) {
+ offset = cinema.second.utc_offset;
+ } else if (cinema.second.utc_offset != *offset) {
+ return dcp::UTCOffset();
+ }
+ }
+
+ return offset;
+}
+
diff --git a/src/lib/cinema_list.h b/src/lib/cinema_list.h
new file mode 100644
index 000000000..c91f29476
--- /dev/null
+++ b/src/lib/cinema_list.h
@@ -0,0 +1,126 @@
+/*
+ Copyright (C) 2023 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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_CINEMA_LIST_H
+#define DCPOMATIC_CINEMA_LIST_H
+
+
+#include "id.h"
+#include "sqlite_table.h"
+#include <libcxml/cxml.h>
+#include <dcp/utc_offset.h>
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+#include <sqlite3.h>
+#include <set>
+
+
+class Cinema;
+namespace dcpomatic {
+ class Screen;
+}
+class SQLiteStatement;
+
+
+class CinemaID : public ID
+{
+public:
+ CinemaID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator<(CinemaID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class ScreenID : public ID
+{
+public:
+ ScreenID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator==(ScreenID const& other) const {
+ return get() == other.get();
+ }
+
+ bool operator!=(ScreenID const& other) const {
+ return get() != other.get();
+ }
+
+ bool operator<(ScreenID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class CinemaList
+{
+public:
+ CinemaList();
+ CinemaList(boost::filesystem::path db_file);
+ ~CinemaList();
+
+ CinemaList(CinemaList const&) = delete;
+ CinemaList& operator=(CinemaList const&) = delete;
+
+ CinemaList(CinemaList&& other);
+ CinemaList& operator=(CinemaList&& other);
+
+ void read_legacy_file(boost::filesystem::path xml_file);
+ void read_legacy_string(std::string const& xml);
+
+ void clear();
+
+ CinemaID add_cinema(Cinema const& cinema);
+ void update_cinema(CinemaID id, Cinema const& cinema);
+ void remove_cinema(CinemaID id);
+ std::vector<std::pair<CinemaID, Cinema>> cinemas() const;
+ boost::optional<Cinema> cinema(CinemaID id) const;
+ boost::optional<std::pair<CinemaID, Cinema>> cinema_by_partial_name(std::string const& text) const;
+ boost::optional<std::pair<CinemaID, Cinema>> cinema_by_name_or_email(std::string const& text) const;
+
+ ScreenID add_screen(CinemaID cinema_id, dcpomatic::Screen const& screen);
+ void update_screen(ScreenID id, dcpomatic::Screen const& screen);
+ void remove_screen(ScreenID id);
+ boost::optional<dcpomatic::Screen> screen(ScreenID screen_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens(CinemaID cinema_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens_by_cinema_and_name(CinemaID id, std::string const& name) const;
+
+ boost::optional<dcp::UTCOffset> unique_utc_offset(std::set<CinemaID> const& cinemas);
+
+private:
+ dcpomatic::Screen screen_from_result(SQLiteStatement& statement, ScreenID screen_id) const;
+ std::vector<std::pair<ScreenID, dcpomatic::Screen>> screens_from_result(SQLiteStatement& statement) const;
+ void setup_tables();
+ void setup(boost::filesystem::path db_file);
+ void read_legacy_document(cxml::Document const& doc);
+
+ sqlite3* _db = nullptr;
+ SQLiteTable _cinemas;
+ SQLiteTable _screens;
+ SQLiteTable _trusted_devices;
+};
+
+
+
+#endif
+
diff --git a/src/lib/config.cc b/src/lib/config.cc
index 33b1a8656..c2c2cc244 100644
--- a/src/lib/config.cc
+++ b/src/lib/config.cc
@@ -19,14 +19,14 @@
*/
-#include "cinema.h"
+#include "cinema_list.h"
#include "colour_conversion.h"
#include "compose.hpp"
#include "config.h"
#include "constants.h"
#include "cross.h"
#include "dcp_content_type.h"
-#include "dkdm_recipient.h"
+#include "dkdm_recipient_list.h"
#include "dkdm_wrapper.h"
#include "film.h"
#include "filter.h"
@@ -139,8 +139,8 @@ Config::set_defaults ()
/* At the moment we don't write these files anywhere new after a version change, so they will be read from
* ~/.config/dcpomatic2 (or equivalent) and written back there.
*/
- _cinemas_file = read_path ("cinemas.xml");
- _dkdm_recipients_file = read_path ("dkdm_recipients.xml");
+ _cinemas_file = read_path("cinemas.sqlite3");
+ _dkdm_recipients_file = read_path("dkdm_recipients.sqlite3");
_show_hints_before_make_dcp = true;
_confirm_kdm_email = true;
_kdm_container_name_format = dcp::NameFormat("KDM_%f_%c");
@@ -276,7 +276,7 @@ Config::backup ()
copy_file(path_to_copy, add_number(path_to_copy, n), ec);
};
- /* Make a backup copy of any config.xml, cinemas.xml, dkdm_recipients.xml that we might be about
+ /* Make a backup copy of any config.xml, cinemas.sqlite3, dkdm_recipients.sqlite3 that we might be about
* to write over. This is more intended for the situation where we have a corrupted config.xml,
* and decide to overwrite it with a new one (possibly losing important details in the corrupted
* file). But we might as well back up the other files while we're about it.
@@ -296,15 +296,6 @@ Config::backup ()
void
Config::read ()
-{
- read_config();
- read_cinemas();
- read_dkdm_recipients();
-}
-
-
-void
-Config::read_config()
try
{
cxml::Document f ("Config");
@@ -406,11 +397,6 @@ try
_default_kdm_directory = f.optional_string_child("DefaultKDMDirectory");
- /* Read any cinemas that are still lying around in the config file
- * from an old version.
- */
- read_cinemas (f);
-
_mail_server = f.string_child ("MailServer");
_mail_port = f.optional_number_child<int> ("MailPort").get_value_or (25);
@@ -548,8 +534,8 @@ try
_dkdms->add (DKDMBase::read (i));
}
}
- _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.xml").string());
- _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.xml").string());
+ _cinemas_file = f.optional_string_child("CinemasFile").get_value_or(read_path("cinemas.sqlite3").string());
+ _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(read_path("dkdm_recipients.sqlite3").string());
_show_hints_before_make_dcp = f.optional_bool_child("ShowHintsBeforeMakeDCP").get_value_or (true);
_confirm_kdm_email = f.optional_bool_child("ConfirmKDMEmail").get_value_or (true);
_kdm_container_name_format = dcp::NameFormat (f.optional_string_child("KDMContainerNameFormat").get_value_or ("KDM %f %c"));
@@ -688,40 +674,6 @@ catch (...) {
}
-void
-Config::read_cinemas()
-{
- if (dcp::filesystem::exists(_cinemas_file)) {
- try {
- cxml::Document f("Cinemas");
- f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
- read_cinemas(f);
- } catch (...) {
- backup();
- FailedToLoad(LoadFailure::CINEMAS);
- write_cinemas();
- }
- }
-}
-
-
-void
-Config::read_dkdm_recipients()
-{
- if (dcp::filesystem::exists(_dkdm_recipients_file)) {
- try {
- cxml::Document f("DKDMRecipients");
- f.read_file(dcp::filesystem::fix_long_path(_dkdm_recipients_file));
- read_dkdm_recipients(f);
- } catch (...) {
- backup();
- FailedToLoad(LoadFailure::DKDM_RECIPIENTS);
- write_dkdm_recipients();
- }
- }
-}
-
-
/** @return Singleton instance */
Config *
Config::instance ()
@@ -729,6 +681,30 @@ Config::instance ()
if (_instance == nullptr) {
_instance = new Config;
_instance->read ();
+
+ auto cinemas_file = _instance->cinemas_file();
+ if (cinemas_file.extension() == ".xml") {
+ auto sqlite = cinemas_file;
+ sqlite.replace_extension(".sqlite3");
+
+ if (dcp::filesystem::exists(cinemas_file) && !dcp::filesystem::exists(sqlite)) {
+ _instance->set_cinemas_file(sqlite);
+ CinemaList cinemas;
+ cinemas.read_legacy_file(cinemas_file);
+ }
+ }
+
+ auto dkdm_recipients_file = _instance->dkdm_recipients_file();
+ if (dkdm_recipients_file.extension() == ".xml") {
+ auto sqlite = dkdm_recipients_file;
+ sqlite.replace_extension(".sqlite3");
+
+ if (dcp::filesystem::exists(dkdm_recipients_file) && !dcp::filesystem::exists(sqlite)) {
+ _instance->set_dkdm_recipients_file(sqlite);
+ DKDMRecipientList recipients;
+ recipients.read_legacy_file(dkdm_recipients_file);
+ }
+ }
}
return _instance;
@@ -739,8 +715,6 @@ void
Config::write () const
{
write_config ();
- write_cinemas ();
- write_dkdm_recipients ();
}
void
@@ -1222,20 +1196,6 @@ write_file (string root_node, string node, string version, list<shared_ptr<T>> t
}
-void
-Config::write_cinemas () const
-{
- write_file ("Cinemas", "Cinema", "1", _cinemas, _cinemas_file);
-}
-
-
-void
-Config::write_dkdm_recipients () const
-{
- write_file ("DKDMRecipients", "DKDMRecipient", "1", _dkdm_recipients, _dkdm_recipients_file);
-}
-
-
boost::filesystem::path
Config::default_directory_or (boost::filesystem::path a) const
{
@@ -1397,20 +1357,6 @@ Config::have_existing (string file)
void
-Config::read_cinemas (cxml::Document const & f)
-{
- _cinemas.clear ();
- for (auto i: f.node_children("Cinema")) {
- /* Slightly grotty two-part construction of Cinema here so that we can use
- shared_from_this.
- */
- auto cinema = make_shared<Cinema>(i);
- cinema->read_screens (i);
- _cinemas.push_back (cinema);
- }
-}
-
-void
Config::set_cinemas_file (boost::filesystem::path file)
{
if (file == _cinemas_file) {
@@ -1419,25 +1365,20 @@ Config::set_cinemas_file (boost::filesystem::path file)
_cinemas_file = file;
- if (dcp::filesystem::exists(_cinemas_file)) {
- /* Existing file; read it in */
- cxml::Document f ("Cinemas");
- f.read_file(dcp::filesystem::fix_long_path(_cinemas_file));
- read_cinemas (f);
- }
-
- changed (CINEMAS);
changed (OTHER);
}
void
-Config::read_dkdm_recipients (cxml::Document const & f)
+Config::set_dkdm_recipients_file(boost::filesystem::path file)
{
- _dkdm_recipients.clear ();
- for (auto i: f.node_children("DKDMRecipient")) {
- _dkdm_recipients.push_back (make_shared<DKDMRecipient>(i));
+ if (file == _dkdm_recipients_file) {
+ return;
}
+
+ _dkdm_recipients_file = file;
+
+ changed(OTHER);
}
@@ -1670,10 +1611,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 (dcp::filesystem::exists(config->cinemas_file())) {
- zipper.add ("cinemas.xml", dcp::file_to_string(config->cinemas_file()));
+ zipper.add("cinemas.sqlite3", dcp::file_to_string(config->cinemas_file()));
}
if (dcp::filesystem::exists(config->dkdm_recipients_file())) {
- zipper.add ("dkdm_recipients.xml", dcp::file_to_string(config->dkdm_recipients_file()));
+ zipper.add("dkdm_recipients.sqlite3", dcp::file_to_string(config->dkdm_recipients_file()));
}
zipper.close ();
@@ -1681,22 +1622,58 @@ save_all_config_as_zip (boost::filesystem::path zip_file)
void
-Config::load_from_zip(boost::filesystem::path zip_file)
+Config::load_from_zip(boost::filesystem::path zip_file, CinemasAction action)
{
+ backup();
+
+ auto const current_cinemas = cinemas_file();
+ /* This is (unfortunately) a full path, and the user can't change it, so
+ * we always want to use that same path in the future no matter what is in the
+ * config.xml that we are about to load.
+ */
+ auto const current_dkdm_recipients = dkdm_recipients_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&) {}
+ if (action == CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+ /* Read the zipped config, so that the cinemas file path is the new one and
+ * we write the cinemas to it.
+ */
+ read();
+ boost::filesystem::create_directories(cinemas_file().parent_path());
+ set_dkdm_recipients_file(current_dkdm_recipients);
+ }
+
+ if (unzipper.contains("cinemas.xml") && action != CinemasAction::IGNORE) {
+ CinemaList cinemas;
+ cinemas.clear();
+ cinemas.read_legacy_string(unzipper.get("cinemas.xml"));
+ }
+
+ if (unzipper.contains("dkdm_recipients.xml")) {
+ DKDMRecipientList recipients;
+ recipients.clear();
+ recipients.read_legacy_string(unzipper.get("dkdm_recipients.xml"));
+ }
- read();
+ if (unzipper.contains("cinemas.sqlite3") && action != CinemasAction::IGNORE) {
+ dcp::write_string_to_file(unzipper.get("cinemas.sqlite3"), cinemas_file());
+ }
+
+ if (unzipper.contains("dkdm_recipients.sqlite3")) {
+ dcp::write_string_to_file(unzipper.get("dkdm_recipients.sqlite3"), dkdm_recipients_file());
+ }
+
+ if (action != CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG) {
+ /* Read the zipped config, then reset the cinemas file to be the old one */
+ read();
+ set_cinemas_file(current_cinemas);
+ set_dkdm_recipients_file(current_dkdm_recipients);
+ }
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);
@@ -1733,6 +1710,25 @@ Config::initial_path(string id) const
}
+bool
+Config::zip_contains_cinemas(boost::filesystem::path zip)
+{
+ Unzipper unzipper(zip);
+ return unzipper.contains("cinemas.sqlite3") || unzipper.contains("cinemas.xml");
+}
+
+
+boost::filesystem::path
+Config::cinemas_file_from_zip(boost::filesystem::path zip)
+{
+ Unzipper unzipper(zip);
+ DCPOMATIC_ASSERT(unzipper.contains("config.xml"));
+ cxml::Document document("Config");
+ document.read_string(unzipper.get("config.xml"));
+ return document.string_child("CinemasFile");
+}
+
+
#ifdef DCPOMATIC_GROK
Config::Grok::Grok(cxml::ConstNodePtr node)
diff --git a/src/lib/config.h b/src/lib/config.h
index a7b238c04..67c784620 100644
--- a/src/lib/config.h
+++ b/src/lib/config.h
@@ -50,6 +50,8 @@ class DKDMRecipient;
class Film;
class Ratio;
+#undef IGNORE
+
extern void save_all_config_as_zip (boost::filesystem::path zip_file);
@@ -81,13 +83,28 @@ public:
boost::filesystem::path default_directory_or (boost::filesystem::path a) const;
boost::filesystem::path default_kdm_directory_or (boost::filesystem::path a) const;
- void load_from_zip(boost::filesystem::path zip_file);
+ enum class CinemasAction
+ {
+ /** Copy the cinemas.{xml,sqlite3} in the ZIP file to the path
+ * specified in the current config, overwriting whatever is there,
+ * and use that path.
+ */
+ WRITE_TO_CURRENT_PATH,
+ /** Copy the cinemas.{xml,sqlite3} in the ZIP file over the path
+ * specified in the config.xml from the ZIP, overwriting whatever
+ * is there and creating any required directories, and use
+ * that path.
+ */
+ WRITE_TO_PATH_IN_ZIPPED_CONFIG,
+ /** Do nothing with the cinemas.{xml,sqlite3} in the ZIP file */
+ IGNORE
+ };
+
+ void load_from_zip(boost::filesystem::path zip_file, CinemasAction action);
enum Property {
USE_ANY_SERVERS,
SERVERS,
- CINEMAS,
- DKDM_RECIPIENTS,
SOUND,
SOUND_OUTPUT,
PLAYER_CONTENT_DIRECTORY,
@@ -163,14 +180,6 @@ public:
return _tms_password;
}
- std::list<std::shared_ptr<Cinema>> cinemas () const {
- return _cinemas;
- }
-
- std::list<std::shared_ptr<DKDMRecipient>> dkdm_recipients () const {
- return _dkdm_recipients;
- }
-
std::list<int> allowed_dcp_frame_rates () const {
return _allowed_dcp_frame_rates;
}
@@ -716,26 +725,6 @@ public:
maybe_set (_tms_password, p);
}
- void add_cinema (std::shared_ptr<Cinema> c) {
- _cinemas.push_back (c);
- changed (CINEMAS);
- }
-
- void remove_cinema (std::shared_ptr<Cinema> c) {
- _cinemas.remove (c);
- changed (CINEMAS);
- }
-
- void add_dkdm_recipient (std::shared_ptr<DKDMRecipient> c) {
- _dkdm_recipients.push_back (c);
- changed (DKDM_RECIPIENTS);
- }
-
- void remove_dkdm_recipient (std::shared_ptr<DKDMRecipient> c) {
- _dkdm_recipients.remove (c);
- changed (DKDM_RECIPIENTS);
- }
-
void set_allowed_dcp_frame_rates (std::list<int> const & r) {
maybe_set (_allowed_dcp_frame_rates, r);
}
@@ -965,6 +954,8 @@ public:
void set_cinemas_file (boost::filesystem::path file);
+ void set_dkdm_recipients_file(boost::filesystem::path file);
+
void set_show_hints_before_make_dcp (bool s) {
maybe_set (_show_hints_before_make_dcp, s);
}
@@ -1254,8 +1245,6 @@ public:
*/
enum class LoadFailure {
CONFIG,
- CINEMAS,
- DKDM_RECIPIENTS
};
static boost::signals2::signal<void (LoadFailure)> FailedToLoad;
/** Emitted if read() issued a warning which the user might want to know about */
@@ -1275,8 +1264,6 @@ public:
void write () const override;
void write_config () const;
- void write_cinemas () const;
- void write_dkdm_recipients () const;
void link (boost::filesystem::path new_file) const;
void copy_and_link (boost::filesystem::path new_file) const;
bool have_write_permission () const;
@@ -1297,6 +1284,9 @@ public:
static bool have_existing (std::string);
static boost::filesystem::path config_read_file ();
static boost::filesystem::path config_write_file ();
+ static bool zip_contains_cinemas(boost::filesystem::path zip);
+ static boost::filesystem::path cinemas_file_from_zip(boost::filesystem::path zip);
+
template <class T>
void maybe_set (T& member, T new_value, Property prop = OTHER) {
@@ -1319,15 +1309,10 @@ public:
private:
Config ();
void read () override;
- void read_config();
- void read_cinemas();
- void read_dkdm_recipients();
void set_defaults ();
void set_kdm_email_to_default ();
void set_notification_email_to_default ();
void set_cover_sheet_to_default ();
- void read_cinemas (cxml::Document const & f);
- void read_dkdm_recipients (cxml::Document const & f);
std::shared_ptr<dcp::CertificateChain> create_certificate_chain ();
boost::filesystem::path directory_or (boost::optional<boost::filesystem::path> dir, boost::filesystem::path a) const;
void add_to_history_internal (std::vector<boost::filesystem::path>& h, boost::filesystem::path p);
@@ -1394,8 +1379,6 @@ private:
*/
boost::optional<boost::filesystem::path> _default_kdm_directory;
bool _upload_after_make_dcp;
- std::list<std::shared_ptr<Cinema>> _cinemas;
- std::list<std::shared_ptr<DKDMRecipient>> _dkdm_recipients;
std::string _mail_server;
int _mail_port;
EmailProtocol _mail_protocol;
diff --git a/src/lib/dkdm_recipient.cc b/src/lib/dkdm_recipient.cc
index 4e56a37c4..83ba96de6 100644
--- a/src/lib/dkdm_recipient.cc
+++ b/src/lib/dkdm_recipient.cc
@@ -19,6 +19,7 @@
*/
+#include "cinema_list.h"
#include "config.h"
#include "dkdm_recipient.h"
#include "film.h"
@@ -33,36 +34,16 @@ using std::vector;
using dcp::raw_convert;
-DKDMRecipient::DKDMRecipient (cxml::ConstNodePtr node)
- : KDMRecipient (node)
-{
- for (auto i: node->node_children("Email")) {
- emails.push_back (i->content());
- }
-}
-
-
-void
-DKDMRecipient::as_xml (xmlpp::Element* node) const
-{
- KDMRecipient::as_xml (node);
-
- for (auto i: emails) {
- cxml::add_text_child(node, "Email", i);
- }
-}
-
-
KDMWithMetadataPtr
kdm_for_dkdm_recipient (
shared_ptr<const Film> film,
boost::filesystem::path cpl,
- shared_ptr<DKDMRecipient> recipient,
+ DKDMRecipient const& recipient,
dcp::LocalTime valid_from,
dcp::LocalTime valid_to
)
{
- if (!recipient->recipient) {
+ if (!recipient.recipient) {
return {};
}
@@ -72,7 +53,7 @@ kdm_for_dkdm_recipient (
}
auto const decrypted_kdm = film->make_kdm(cpl, valid_from, valid_to);
- auto const kdm = decrypted_kdm.encrypt(signer, recipient->recipient.get(), {}, dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, 0);
+ auto const kdm = decrypted_kdm.encrypt(signer, recipient.recipient.get(), {}, dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, 0);
dcp::NameFormat::Map name_values;
name_values['f'] = kdm.content_title_text();
@@ -80,6 +61,6 @@ kdm_for_dkdm_recipient (
name_values['e'] = valid_to.date() + " " + valid_to.time_of_day(true, false);
name_values['i'] = kdm.cpl_id();
- return make_shared<KDMWithMetadata>(name_values, nullptr, recipient->emails, kdm);
+ return make_shared<KDMWithMetadata>(name_values, CinemaID(0), recipient.emails, kdm);
}
diff --git a/src/lib/dkdm_recipient.h b/src/lib/dkdm_recipient.h
index 3317ae6f9..64da41cff 100644
--- a/src/lib/dkdm_recipient.h
+++ b/src/lib/dkdm_recipient.h
@@ -41,10 +41,6 @@ public:
}
- explicit DKDMRecipient (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const override;
-
std::vector<std::string> emails;
};
@@ -53,7 +49,7 @@ KDMWithMetadataPtr
kdm_for_dkdm_recipient (
std::shared_ptr<const Film> film,
boost::filesystem::path cpl,
- std::shared_ptr<DKDMRecipient> recipient,
+ DKDMRecipient const& recipient,
dcp::LocalTime valid_from,
dcp::LocalTime valid_to
);
diff --git a/src/lib/dkdm_recipient_list.cc b/src/lib/dkdm_recipient_list.cc
new file mode 100644
index 000000000..34179337e
--- /dev/null
+++ b/src/lib/dkdm_recipient_list.cc
@@ -0,0 +1,243 @@
+/*
+ Copyright (C) 2024 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 "config.h"
+#include "dkdm_recipient.h"
+#include "dkdm_recipient_list.h"
+#include "sqlite_statement.h"
+#include "sqlite_transaction.h"
+#include "util.h"
+#include <boost/algorithm/string.hpp>
+
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+using boost::optional;
+
+
+DKDMRecipientList::DKDMRecipientList()
+ : _dkdm_recipients("dkdm_recipients")
+{
+ setup(Config::instance()->dkdm_recipients_file());
+}
+
+
+DKDMRecipientList::DKDMRecipientList(boost::filesystem::path db_file)
+ : _dkdm_recipients("dkdm_recipients")
+{
+ setup(db_file);
+}
+
+
+
+DKDMRecipientList::~DKDMRecipientList()
+{
+ if (_db) {
+ sqlite3_close(_db);
+ }
+}
+
+
+void
+DKDMRecipientList::read_legacy_file(boost::filesystem::path xml_file)
+{
+ cxml::Document doc("DKDMRecipients");
+ doc.read_file(xml_file);
+
+ read_legacy_document(doc);
+}
+
+
+void
+DKDMRecipientList::read_legacy_string(string const& xml)
+{
+ cxml::Document doc("DKDMRecipients");
+ doc.read_file(xml);
+
+ read_legacy_document(doc);
+}
+
+
+void
+DKDMRecipientList::read_legacy_document(cxml::Document const& doc)
+{
+ for (auto recipient_node: doc.node_children("DKDMRecipient")) {
+ vector<string> emails;
+ for (auto email_node: recipient_node->node_children("Email")) {
+ emails.push_back(email_node->content());
+ }
+
+ optional<dcp::Certificate> certificate;
+ if (auto certificate_string = recipient_node->optional_string_child("Recipient")) {
+ certificate = dcp::Certificate(*certificate_string);
+ }
+
+ DKDMRecipient recipient(
+ recipient_node->string_child("Name"),
+ recipient_node->string_child("Notes"),
+ certificate,
+ emails
+ );
+
+ add_dkdm_recipient(recipient);
+ }
+}
+
+
+void
+DKDMRecipientList::setup(boost::filesystem::path db_file)
+{
+ _dkdm_recipients.add_column("name", "TEXT");
+ _dkdm_recipients.add_column("notes", "TEXT");
+ _dkdm_recipients.add_column("recipient", "TEXT");
+ _dkdm_recipients.add_column("emails", "TEXT");
+
+#ifdef DCPOMATIC_WINDOWS
+ auto rc = sqlite3_open16(db_file.c_str(), &_db);
+#else
+ auto rc = sqlite3_open(db_file.c_str(), &_db);
+#endif
+ if (rc != SQLITE_OK) {
+ throw FileError("Could not open SQLite database", db_file);
+ }
+
+ sqlite3_busy_timeout(_db, 500);
+
+ SQLiteStatement screens(_db, _dkdm_recipients.create());
+ screens.execute();
+}
+
+
+DKDMRecipientList::DKDMRecipientList(DKDMRecipientList&& other)
+ : _dkdm_recipients(std::move(other._dkdm_recipients))
+{
+ _db = other._db;
+ other._db = nullptr;
+}
+
+
+DKDMRecipientList&
+DKDMRecipientList::operator=(DKDMRecipientList&& other)
+{
+ if (this != &other) {
+ _db = other._db;
+ other._db = nullptr;
+ }
+ return *this;
+}
+
+
+DKDMRecipientID
+DKDMRecipientList::add_dkdm_recipient(DKDMRecipient const& dkdm_recipient)
+{
+ SQLiteStatement add_dkdm_recipient(_db, _dkdm_recipients.insert());
+
+ add_dkdm_recipient.bind_text(1, dkdm_recipient.name);
+ add_dkdm_recipient.bind_text(2, dkdm_recipient.notes);
+ add_dkdm_recipient.bind_text(3, dkdm_recipient.recipient ? dkdm_recipient.recipient->certificate(true) : "");
+ add_dkdm_recipient.bind_text(4, join_strings(dkdm_recipient.emails));
+
+ add_dkdm_recipient.execute();
+
+ return sqlite3_last_insert_rowid(_db);
+}
+
+
+void
+DKDMRecipientList::update_dkdm_recipient(DKDMRecipientID id, DKDMRecipient const& dkdm_recipient)
+{
+ SQLiteStatement add_dkdm_recipient(_db, _dkdm_recipients.update("WHERE id=?"));
+
+ add_dkdm_recipient.bind_text(1, dkdm_recipient.name);
+ add_dkdm_recipient.bind_text(2, dkdm_recipient.notes);
+ add_dkdm_recipient.bind_text(3, dkdm_recipient.recipient ? dkdm_recipient.recipient->certificate(true) : "");
+ add_dkdm_recipient.bind_text(4, join_strings(dkdm_recipient.emails));
+ add_dkdm_recipient.bind_int64(5, id.get());
+
+ add_dkdm_recipient.execute();
+}
+
+
+void
+DKDMRecipientList::remove_dkdm_recipient(DKDMRecipientID id)
+{
+ SQLiteStatement statement(_db, "DELETE FROM dkdm_recipients WHERE ID=?");
+ statement.bind_int64(1, id.get());
+ statement.execute();
+}
+
+
+static
+vector<pair<DKDMRecipientID, DKDMRecipient>>
+dkdm_recipients_from_result(SQLiteStatement& statement)
+{
+ vector<pair<DKDMRecipientID, DKDMRecipient>> output;
+
+ statement.execute([&output](SQLiteStatement& statement) {
+ DCPOMATIC_ASSERT(statement.data_count() == 5);
+ DKDMRecipientID const id = statement.column_int64(0);
+ auto const name = statement.column_text(1);
+ auto const notes = statement.column_text(2);
+ auto certificate_string = statement.column_text(3);
+ optional<dcp::Certificate> certificate = certificate_string.empty() ? optional<dcp::Certificate>() : dcp::Certificate(certificate_string);
+ auto const join_with_spaces = statement.column_text(4);
+ vector<string> emails;
+ boost::algorithm::split(emails, join_with_spaces, boost::is_any_of(" "));
+ output.push_back(make_pair(id, DKDMRecipient(name, notes, certificate, { emails })));
+ });
+
+ return output;
+}
+
+
+
+
+vector<std::pair<DKDMRecipientID, DKDMRecipient>>
+DKDMRecipientList::dkdm_recipients() const
+{
+ SQLiteStatement statement(_db, _dkdm_recipients.select("ORDER BY name ASC"));
+ return dkdm_recipients_from_result(statement);
+}
+
+
+boost::optional<DKDMRecipient>
+DKDMRecipientList::dkdm_recipient(DKDMRecipientID id) const
+{
+ SQLiteStatement statement(_db, _dkdm_recipients.select("WHERE id=?"));
+ statement.bind_int64(1, id.get());
+ auto result = dkdm_recipients_from_result(statement);
+ if (result.empty()) {
+ return {};
+ }
+ return result[0].second;
+}
+
+
+void
+DKDMRecipientList::clear()
+{
+ SQLiteStatement sql(_db, "DELETE FROM dkdm_recipients");
+ sql.execute();
+}
+
+
diff --git a/src/lib/dkdm_recipient_list.h b/src/lib/dkdm_recipient_list.h
new file mode 100644
index 000000000..fc4d84b60
--- /dev/null
+++ b/src/lib/dkdm_recipient_list.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2024 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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_DKDM_RECIPIENT_LIST_H
+#define DCPOMATIC_DKDM_RECIPIENT_LIST_H
+
+
+#include "id.h"
+#include "sqlite_table.h"
+#include <libcxml/cxml.h>
+#include <boost/filesystem.hpp>
+#include <boost/optional.hpp>
+
+
+class DKDMRecipient;
+
+
+class DKDMRecipientID : public ID
+{
+public:
+ DKDMRecipientID(sqlite3_int64 id)
+ : ID(id) {}
+
+ bool operator==(DKDMRecipientID const& other) const {
+ return get() == other.get();
+ }
+
+ bool operator!=(DKDMRecipientID const& other) const {
+ return get() != other.get();
+ }
+
+ bool operator<(DKDMRecipientID const& other) const {
+ return get() < other.get();
+ }
+};
+
+
+class DKDMRecipientList
+{
+public:
+ DKDMRecipientList();
+ DKDMRecipientList(boost::filesystem::path db_file);
+ ~DKDMRecipientList();
+
+ DKDMRecipientList(DKDMRecipientList const&) = delete;
+ DKDMRecipientList& operator=(DKDMRecipientList const&) = delete;
+
+ DKDMRecipientList(DKDMRecipientList&& other);
+ DKDMRecipientList& operator=(DKDMRecipientList&& other);
+
+ void read_legacy_file(boost::filesystem::path xml_file);
+ void read_legacy_string(std::string const& xml);
+
+ void clear();
+
+ DKDMRecipientID add_dkdm_recipient(DKDMRecipient const& dkdm_recipient);
+ void update_dkdm_recipient(DKDMRecipientID id, DKDMRecipient const& dkdm_recipient);
+ void remove_dkdm_recipient(DKDMRecipientID id);
+ std::vector<std::pair<DKDMRecipientID, DKDMRecipient>> dkdm_recipients() const;
+ boost::optional<DKDMRecipient> dkdm_recipient(DKDMRecipientID id) const;
+
+private:
+ void setup(boost::filesystem::path db_file);
+ void read_legacy_document(cxml::Document const& doc);
+
+ sqlite3* _db = nullptr;
+ SQLiteTable _dkdm_recipients;
+};
+
+
+#endif
+
diff --git a/src/lib/exceptions.h b/src/lib/exceptions.h
index 10231f59a..e08392689 100644
--- a/src/lib/exceptions.h
+++ b/src/lib/exceptions.h
@@ -34,6 +34,7 @@ extern "C" {
}
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
+#include <sqlite3.h>
#include <cstring>
#include <stdexcept>
@@ -480,4 +481,58 @@ public:
};
+class SQLError : public std::runtime_error
+{
+public:
+ SQLError(sqlite3* db, char const* s)
+ : std::runtime_error(get_message(db, s))
+ {
+ _filename = get_filename(db);
+ }
+
+ SQLError(sqlite3* db, int rc)
+ : std::runtime_error(get_message(db, rc))
+ {
+ _filename = get_filename(db);
+ }
+
+ SQLError(sqlite3* db, int rc, std::string doing)
+ : std::runtime_error(get_message(db, rc, doing))
+ {
+ _filename = get_filename(db);
+ }
+
+ boost::filesystem::path filename() const {
+ return _filename;
+ }
+
+private:
+ boost::filesystem::path get_filename(sqlite3* db)
+ {
+ if (auto filename = sqlite3_db_filename(db, "main")) {
+ return filename;
+ }
+
+ return {};
+ }
+
+ std::string get_message(sqlite3* db, char const* s)
+ {
+ return String::compose("%1 (in %2)", s, get_filename(db));
+ }
+
+ std::string get_message(sqlite3* db, int rc)
+ {
+ return String::compose("%1 (in %2)", sqlite3_errstr(rc), get_filename(db));
+ }
+
+ std::string get_message(sqlite3* db, int rc, std::string doing)
+ {
+ return String::compose("%1 (while doing %2) (in %3)", sqlite3_errstr(rc), doing, get_filename(db));
+ }
+
+ boost::filesystem::path _filename;
+};
+
+
#endif
diff --git a/src/lib/id.cc b/src/lib/id.cc
new file mode 100644
index 000000000..2891fb4ab
--- /dev/null
+++ b/src/lib/id.cc
@@ -0,0 +1,30 @@
+/*
+ Copyright (C) 2023 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 "id.h"
+
+
+bool
+operator==(ID const& a, ID const& b)
+{
+ return a.get() == b.get();
+}
+
diff --git a/src/lib/id.h b/src/lib/id.h
new file mode 100644
index 000000000..ca4721039
--- /dev/null
+++ b/src/lib/id.h
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2023 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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_ID_H
+#define DCPOMATIC_ID_H
+
+
+#include <sqlite3.h>
+
+
+class ID
+{
+public:
+ sqlite3_int64 get() const {
+ return _id;
+ }
+
+protected:
+ ID(sqlite3_int64 id)
+ : _id(id) {}
+
+private:
+ sqlite3_int64 _id;
+};
+
+
+bool operator==(ID const& a, ID const& b);
+
+
+#endif
diff --git a/src/lib/kdm_cli.cc b/src/lib/kdm_cli.cc
index 21e8c75d3..9d5b54b9a 100644
--- a/src/lib/kdm_cli.cc
+++ b/src/lib/kdm_cli.cc
@@ -25,6 +25,7 @@
#include "cinema.h"
+#include "cinema_list.h"
#include "config.h"
#include "dkdm_wrapper.h"
#include "email.h"
@@ -43,6 +44,7 @@
using std::dynamic_pointer_cast;
using std::list;
using std::make_shared;
+using std::pair;
using std::runtime_error;
using std::shared_ptr;
using std::string;
@@ -175,32 +177,25 @@ write_files (
}
-static
-shared_ptr<Cinema>
-find_cinema (string cinema_name)
+class ScreenDetails
{
- auto cinemas = Config::instance()->cinemas ();
- auto i = cinemas.begin();
- while (
- i != cinemas.end() &&
- (*i)->name != cinema_name &&
- find ((*i)->emails.begin(), (*i)->emails.end(), cinema_name) == (*i)->emails.end()) {
-
- ++i;
- }
-
- if (i == cinemas.end ()) {
- throw KDMCLIError (String::compose("could not find cinema \"%1\"", cinema_name));
- }
+public:
+ ScreenDetails(CinemaID const& cinema_id, Cinema const& cinema, Screen const& screen)
+ : cinema_id(cinema_id)
+ , cinema(cinema)
+ , screen(screen)
+ {}
- return *i;
-}
+ CinemaID cinema_id;
+ Cinema cinema;
+ Screen screen;
+};
static
void
from_film (
- vector<shared_ptr<Screen>> screens,
+ vector<ScreenDetails> const& screens,
boost::filesystem::path film_dir,
bool verbose,
boost::filesystem::path output,
@@ -241,11 +236,22 @@ from_film (
try {
list<KDMWithMetadataPtr> kdms;
- for (auto i: screens) {
+ for (auto screen_details: screens) {
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm = [film, cpl](dcp::LocalTime begin, dcp::LocalTime end) {
return film->make_kdm(cpl, begin, end);
};
- auto p = kdm_for_screen(make_kdm, i, valid_from, valid_to, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio, period_checks);
+ auto p = kdm_for_screen(
+ make_kdm,
+ screen_details.cinema_id,
+ screen_details.cinema,
+ screen_details.screen,
+ valid_from,
+ valid_to,
+ formulation,
+ disable_forensic_marking_picture,
+ disable_forensic_marking_audio,
+ period_checks
+ );
if (p) {
kdms.push_back (p);
}
@@ -350,7 +356,7 @@ kdm_from_dkdm (
static
void
from_dkdm (
- vector<shared_ptr<Screen>> screens,
+ vector<ScreenDetails> const& screens,
dcp::DecryptedKDM dkdm,
bool verbose,
boost::filesystem::path output,
@@ -370,15 +376,15 @@ from_dkdm (
try {
list<KDMWithMetadataPtr> kdms;
- for (auto i: screens) {
- if (!i->recipient) {
+ for (auto const& screen_details: screens) {
+ if (!screen_details.screen.recipient) {
continue;
}
auto const kdm = kdm_from_dkdm(
dkdm,
- i->recipient.get(),
- i->trusted_device_thumbprints(),
+ screen_details.screen.recipient.get(),
+ screen_details.screen.trusted_device_thumbprints(),
valid_from,
valid_to,
formulation,
@@ -387,14 +393,14 @@ from_dkdm (
);
dcp::NameFormat::Map name_values;
- name_values['c'] = i->cinema ? i->cinema->name : "";
- name_values['s'] = i->name;
+ name_values['c'] = screen_details.cinema.name;
+ name_values['s'] = screen_details.screen.name;
name_values['f'] = kdm.content_title_text();
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));
+ kdms.push_back(make_shared<KDMWithMetadata>(name_values, screen_details.cinema_id, screen_details.cinema.emails, kdm));
}
write_files (kdms, zip, output, container_name_format, filename_format, verbose, out);
if (email) {
@@ -451,12 +457,15 @@ try
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();
+ /* either a cinema name to search for, or the name of a cinema to associate with certificate */
optional<string> cinema_name;
- shared_ptr<Cinema> cinema;
+ /* either a screen name to search for, or the name of a screen to associate with certificate */
+ optional<string> screen_name;
+ /* a certificate that we will use to make up a temporary cinema and screen */
optional<boost::filesystem::path> projector_certificate;
optional<boost::filesystem::path> decryption_key;
- optional<string> screen;
- vector<shared_ptr<Screen>> screens;
+ /* trusted devices that we will use to make up a temporary cinema and screen */
+ vector<TrustedDevice> trusted_devices;
optional<dcp::EncryptedKDM> dkdm;
optional<dcp::LocalTime> valid_from;
optional<dcp::LocalTime> valid_to;
@@ -562,27 +571,16 @@ try
verbose = true;
break;
case 'c':
- /* This could be a cinema to search for in the configured list or the name of a cinema being
- built up on-the-fly in the option. Cater for both possilibities here by storing the name
- (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>(), "", dcp::UTCOffset());
break;
case 'S':
- /* Similarly, this could be the name of a new (temporary) screen or the name of a screen
- * to search for.
- */
- screen = optarg;
+ screen_name = optarg;
break;
case 'C':
projector_certificate = optarg;
break;
case 'T':
- /* A trusted device ends up in the last screen we made */
- if (!screens.empty ()) {
- screens.back()->trusted_devices.push_back(TrustedDevice(dcp::Certificate(dcp::file_to_string(optarg))));
- }
+ trusted_devices.push_back(TrustedDevice(dcp::Certificate(dcp::file_to_string(optarg))));
break;
case 'G':
decryption_key = optarg;
@@ -618,20 +616,21 @@ try
Config::instance()->set_cinemas_file(*cinemas_file);
}
+ /* If we've been given a certificate we can make up a temporary cinema and screen (not written to the
+ * database) to then use for making KDMs.
+ */
+ optional<Cinema> temp_cinema;
+ optional<Screen> temp_screen;
if (projector_certificate) {
- /* Make a new screen and add it to the current cinema */
+ temp_cinema = Cinema(cinema_name.get_value_or(""), {}, "", dcp::UTCOffset());
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);
- }
- screens.push_back(screen_to_add);
+ temp_screen = Screen(screen_name.get_value_or(""), "", chain.leaf(), boost::none, trusted_devices);
}
if (command == "list-cinemas") {
- auto cinemas = Config::instance()->cinemas ();
- for (auto i: cinemas) {
- out (String::compose("%1 (%2)", i->name, Email::address_list(i->emails)));
+ CinemaList cinemas;
+ for (auto const& cinema: cinemas.cinemas()) {
+ out(String::compose("%1 (%2)", cinema.second.name, Email::address_list(cinema.second.emails)));
}
return {};
}
@@ -660,15 +659,32 @@ try
throw KDMCLIError ("you must specify --valid-from");
}
- if (screens.empty()) {
+ if (optind >= argc) {
+ throw KDMCLIError ("no film, CPL ID or DKDM specified");
+ }
+
+ vector<ScreenDetails> screens;
+
+ if (!temp_cinema) {
if (!cinema_name) {
- throw KDMCLIError ("you must specify either a cinema or one or more screens using certificate files");
+ throw KDMCLIError("you must specify either a cinema or one or more screens using certificate files");
}
- screens = find_cinema (*cinema_name)->screens ();
- if (screen) {
- screens.erase(std::remove_if(screens.begin(), screens.end(), [&screen](shared_ptr<Screen> s) { return s->name != *screen; }), screens.end());
+ CinemaList cinema_list;
+ if (auto cinema = cinema_list.cinema_by_name_or_email(*cinema_name)) {
+ if (screen_name) {
+ for (auto screen: cinema_list.screens_by_cinema_and_name(cinema->first, *screen_name)) {
+ screens.push_back({cinema->first, cinema->second, screen.second});
+ }
+ } else {
+ for (auto screen: cinema_list.screens(cinema->first)) {
+ screens.push_back({cinema->first, cinema->second, screen.second});
+ }
+ }
}
+ } else {
+ DCPOMATIC_ASSERT(temp_screen);
+ screens.push_back({CinemaID(0), *temp_cinema, *temp_screen});
}
if (duration_string) {
diff --git a/src/lib/kdm_with_metadata.h b/src/lib/kdm_with_metadata.h
index fbeeffbc1..6198564b1 100644
--- a/src/lib/kdm_with_metadata.h
+++ b/src/lib/kdm_with_metadata.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_KDM_WITH_METADATA_H
+#include "id.h"
#include <dcp/encrypted_kdm.h>
#include <dcp/name_format.h>
@@ -33,7 +34,7 @@ class Cinema;
class KDMWithMetadata
{
public:
- KDMWithMetadata(dcp::NameFormat::Map const& name_values, void const* group, std::vector<std::string> emails, dcp::EncryptedKDM kdm)
+ KDMWithMetadata(dcp::NameFormat::Map const& name_values, ID group, std::vector<std::string> emails, dcp::EncryptedKDM kdm)
: _name_values (name_values)
, _group (group)
, _emails (emails)
@@ -54,7 +55,7 @@ public:
boost::optional<std::string> get (char k) const;
- void const* group () const {
+ ID group() const {
return _group;
}
@@ -64,7 +65,7 @@ public:
private:
dcp::NameFormat::Map _name_values;
- void const* _group;
+ ID _group;
std::vector<std::string> _emails;
dcp::EncryptedKDM _kdm;
};
diff --git a/src/lib/screen.cc b/src/lib/screen.cc
index 38c474850..b77eb6b52 100644
--- a/src/lib/screen.cc
+++ b/src/lib/screen.cc
@@ -20,6 +20,7 @@
#include "cinema.h"
+#include "cinema_list.h"
#include "config.h"
#include "film.h"
#include "kdm_util.h"
@@ -39,29 +40,6 @@ using boost::optional;
using namespace dcpomatic;
-Screen::Screen (cxml::ConstNodePtr node)
- : KDMRecipient (node)
-{
- for (auto i: node->node_children ("TrustedDevice")) {
- if (boost::algorithm::starts_with(i->content(), "-----BEGIN CERTIFICATE-----")) {
- trusted_devices.push_back (TrustedDevice(dcp::Certificate(i->content())));
- } else {
- trusted_devices.push_back (TrustedDevice(i->content()));
- }
- }
-}
-
-
-void
-Screen::as_xml (xmlpp::Element* parent) const
-{
- KDMRecipient::as_xml (parent);
- for (auto i: trusted_devices) {
- cxml::add_text_child(parent, "TrustedDevice", i.as_string());
- }
-}
-
-
vector<string>
Screen::trusted_device_thumbprints () const
{
@@ -76,7 +54,9 @@ Screen::trusted_device_thumbprints () const
KDMWithMetadataPtr
kdm_for_screen (
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm,
- shared_ptr<const dcpomatic::Screen> screen,
+ CinemaID cinema_id,
+ Cinema const& cinema,
+ Screen const& screen,
dcp::LocalTime valid_from,
dcp::LocalTime valid_to,
dcp::Formulation formulation,
@@ -85,13 +65,11 @@ kdm_for_screen (
vector<KDMCertificatePeriod>& period_checks
)
{
- if (!screen->recipient) {
+ if (!screen.recipient) {
return {};
}
- auto cinema = screen->cinema;
-
- period_checks.push_back(check_kdm_and_certificate_validity_periods(cinema ? cinema->name : "", screen->name, screen->recipient.get(), valid_from, valid_to));
+ period_checks.push_back(check_kdm_and_certificate_validity_periods(cinema.name, screen.name, screen.recipient.get(), valid_from, valid_to));
auto signer = Config::instance()->signer_chain();
if (!signer->valid()) {
@@ -99,21 +77,17 @@ kdm_for_screen (
}
auto kdm = make_kdm(valid_from, valid_to).encrypt(
- signer, screen->recipient.get(), screen->trusted_device_thumbprints(), formulation, disable_forensic_marking_picture, disable_forensic_marking_audio
+ signer, screen.recipient.get(), screen.trusted_device_thumbprints(), formulation, disable_forensic_marking_picture, disable_forensic_marking_audio
);
dcp::NameFormat::Map name_values;
- if (cinema) {
- name_values['c'] = cinema->name;
- } else {
- name_values['c'] = "";
- }
- name_values['s'] = screen->name;
+ name_values['c'] = cinema.name;
+ name_values['s'] = screen.name;
name_values['f'] = kdm.content_title_text();
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();
- return make_shared<KDMWithMetadata>(name_values, cinema.get(), cinema ? cinema->emails : vector<string>(), kdm);
+ return make_shared<KDMWithMetadata>(name_values, cinema_id, cinema.emails, kdm);
}
diff --git a/src/lib/screen.h b/src/lib/screen.h
index 0a275aa34..89ebc3ab4 100644
--- a/src/lib/screen.h
+++ b/src/lib/screen.h
@@ -23,6 +23,7 @@
#define DCPOMATIC_SCREEN_H
+#include "cinema_list.h"
#include "kdm_recipient.h"
#include "kdm_util.h"
#include "kdm_with_metadata.h"
@@ -63,12 +64,7 @@ public:
, trusted_devices (trusted_devices_)
{}
- explicit Screen (cxml::ConstNodePtr);
-
- void as_xml (xmlpp::Element *) const override;
std::vector<std::string> trusted_device_thumbprints () const;
-
- std::shared_ptr<Cinema> cinema;
std::vector<TrustedDevice> trusted_devices;
};
@@ -78,7 +74,9 @@ public:
KDMWithMetadataPtr
kdm_for_screen (
std::function<dcp::DecryptedKDM (dcp::LocalTime, dcp::LocalTime)> make_kdm,
- std::shared_ptr<const dcpomatic::Screen> screen,
+ CinemaID cinema_id,
+ Cinema const& cinema,
+ dcpomatic::Screen const& screen,
dcp::LocalTime valid_from,
dcp::LocalTime valid_to,
dcp::Formulation formulation,
diff --git a/src/lib/sqlite_statement.cc b/src/lib/sqlite_statement.cc
new file mode 100644
index 000000000..b3ec1fb81
--- /dev/null
+++ b/src/lib/sqlite_statement.cc
@@ -0,0 +1,111 @@
+/*
+ Copyright (C) 2023 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 "exceptions.h"
+#include "sqlite_statement.h"
+
+
+using std::function;
+using std::string;
+
+
+SQLiteStatement::SQLiteStatement(sqlite3* db, string const& statement)
+ : _db(db)
+{
+#ifdef DCPOMATIC_HAVE_SQLITE3_PREPARE_V3
+ auto rc = sqlite3_prepare_v3(_db, statement.c_str(), -1, 0, &_stmt, nullptr);
+#else
+ auto rc = sqlite3_prepare_v2(_db, statement.c_str(), -1, &_stmt, nullptr);
+#endif
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc, statement);
+ }
+}
+
+
+SQLiteStatement::~SQLiteStatement()
+{
+ sqlite3_finalize(_stmt);
+}
+
+
+void
+SQLiteStatement::bind_text(int index, string const& value)
+{
+ auto rc = sqlite3_bind_text(_stmt, index, value.c_str(), -1, SQLITE_TRANSIENT);
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc);
+ }
+}
+
+
+void
+SQLiteStatement::bind_int64(int index, int64_t value)
+{
+ auto rc = sqlite3_bind_int64(_stmt, index, value);
+ if (rc != SQLITE_OK) {
+ throw SQLError(_db, rc);
+ }
+}
+
+
+void
+SQLiteStatement::execute(function<void(SQLiteStatement&)> row, function<void()> busy)
+{
+ while (true) {
+ auto const rc = sqlite3_step(_stmt);
+ switch (rc) {
+ case SQLITE_BUSY:
+ busy();
+ break;
+ case SQLITE_DONE:
+ return;
+ case SQLITE_ROW:
+ row(*this);
+ break;
+ case SQLITE_ERROR:
+ case SQLITE_MISUSE:
+ throw SQLError(_db, sqlite3_errmsg(_db));
+ }
+ }
+}
+
+
+int
+SQLiteStatement::data_count()
+{
+ return sqlite3_data_count(_stmt);
+}
+
+
+int64_t
+SQLiteStatement::column_int64(int index)
+{
+ return sqlite3_column_int64(_stmt, index);
+}
+
+
+string
+SQLiteStatement::column_text(int index)
+{
+ return reinterpret_cast<const char*>(sqlite3_column_text(_stmt, index));
+}
+
diff --git a/src/lib/sqlite_statement.h b/src/lib/sqlite_statement.h
new file mode 100644
index 000000000..3c2246efb
--- /dev/null
+++ b/src/lib/sqlite_statement.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2023 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 <sqlite3.h>
+#include <functional>
+#include <string>
+
+
+class SQLiteStatement
+{
+public:
+ SQLiteStatement(sqlite3* db, std::string const& statement);
+ ~SQLiteStatement();
+
+ SQLiteStatement(SQLiteStatement const&) = delete;
+ SQLiteStatement& operator=(SQLiteStatement const&) = delete;
+
+ void bind_text(int index, std::string const& value);
+ void bind_int64(int index, int64_t value);
+
+ int64_t column_int64(int index);
+ std::string column_text(int index);
+
+ void execute(std::function<void(SQLiteStatement&)> row = std::function<void(SQLiteStatement& statement)>(), std::function<void()> busy = std::function<void()>());
+
+ int data_count();
+
+private:
+ sqlite3* _db;
+ sqlite3_stmt* _stmt;
+};
+
diff --git a/src/lib/sqlite_table.cc b/src/lib/sqlite_table.cc
new file mode 100644
index 000000000..f00fa6b74
--- /dev/null
+++ b/src/lib/sqlite_table.cc
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2024 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 "dcpomatic_assert.h"
+#include "compose.hpp"
+#include "sqlite_table.h"
+#include "util.h"
+
+
+using std::string;
+using std::vector;
+
+
+void
+SQLiteTable::add_column(string const& name, string const& type)
+{
+ _columns.push_back(name);
+ _types.push_back(type);
+}
+
+
+string
+SQLiteTable::create() const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ DCPOMATIC_ASSERT(_columns.size() == _types.size());
+ vector<string> columns(_columns.size());
+ for (size_t i = 0; i < _columns.size(); ++i) {
+ columns[i] = _columns[i] + " " + _types[i];
+ }
+ return String::compose("CREATE TABLE IF NOT EXISTS %1 (id INTEGER PRIMARY KEY, %2)", _name, join_strings(columns, ", "));
+}
+
+
+string
+SQLiteTable::insert() const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ vector<string> placeholders(_columns.size(), "?");
+ return String::compose("INSERT INTO %1 (%2) VALUES (%3)", _name, join_strings(_columns, ", "), join_strings(placeholders, ", "));
+}
+
+
+string
+SQLiteTable::update(string const& condition) const
+{
+ DCPOMATIC_ASSERT(!_columns.empty());
+ vector<string> placeholders(_columns.size());
+ for (size_t i = 0; i < _columns.size(); ++i) {
+ placeholders[i] = _columns[i] + "=?";
+ }
+
+ return String::compose("UPDATE %1 SET %2 %3", _name, join_strings(placeholders, ", "), condition);
+}
+
+
+string
+SQLiteTable::select(string const& condition) const
+{
+ return String::compose("SELECT id,%1 FROM %2 %3", join_strings(_columns, ","), _name, condition);
+}
diff --git a/src/lib/sqlite_table.h b/src/lib/sqlite_table.h
new file mode 100644
index 000000000..43c9491ed
--- /dev/null
+++ b/src/lib/sqlite_table.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2024 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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_SQLITE_TABLE_H
+#define DCPOMATIC_SQLITE_TABLE_H
+
+#include <string>
+#include <vector>
+
+
+class SQLiteTable
+{
+public:
+ SQLiteTable(std::string name)
+ : _name(std::move(name))
+ {}
+
+ SQLiteTable(SQLiteTable const&) = default;
+ SQLiteTable(SQLiteTable&&) = default;
+
+ void add_column(std::string const& name, std::string const& type);
+
+ std::string create() const;
+ std::string insert() const;
+ std::string update(std::string const& condition) const;
+ std::string select(std::string const& condition) const;
+
+private:
+ std::string _name;
+ std::vector<std::string> _columns;
+ std::vector<std::string> _types;
+};
+
+
+#endif
+
diff --git a/src/lib/sqlite_transaction.cc b/src/lib/sqlite_transaction.cc
new file mode 100644
index 000000000..239d85020
--- /dev/null
+++ b/src/lib/sqlite_transaction.cc
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 2023 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 "sqlite_statement.h"
+#include "sqlite_transaction.h"
+
+
+SQLiteTransaction::SQLiteTransaction(sqlite3* db)
+ : _db(db)
+{
+ SQLiteStatement statement(_db, "BEGIN TRANSACTION");
+ statement.execute();
+}
+
+
+SQLiteTransaction::~SQLiteTransaction()
+{
+ if (_rollback) {
+ SQLiteStatement rollback(_db, "ROLLBACK");
+ rollback.execute();
+ }
+}
+
+
+void
+SQLiteTransaction::commit()
+{
+ SQLiteStatement commit(_db, "COMMIT");
+ commit.execute();
+ _rollback = false;
+}
+
diff --git a/src/lib/sqlite_transaction.h b/src/lib/sqlite_transaction.h
new file mode 100644
index 000000000..0f6319243
--- /dev/null
+++ b/src/lib/sqlite_transaction.h
@@ -0,0 +1,40 @@
+/*
+ Copyright (C) 2023 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 <sqlite3.h>
+
+
+class SQLiteTransaction
+{
+public:
+ SQLiteTransaction(sqlite3* db);
+ ~SQLiteTransaction();
+
+ SQLiteTransaction(SQLiteTransaction const&) = delete;
+ SQLiteTransaction& operator=(SQLiteTransaction const&) = delete;
+
+ void commit();
+
+private:
+ sqlite3* _db;
+ bool _rollback = true;
+};
+
diff --git a/src/lib/unzipper.cc b/src/lib/unzipper.cc
index f0170e7e0..8d468f24f 100644
--- a/src/lib/unzipper.cc
+++ b/src/lib/unzipper.cc
@@ -56,8 +56,20 @@ Unzipper::~Unzipper()
}
+bool
+Unzipper::contains(string const& filename) const
+{
+ auto file = zip_fopen(_zip, filename.c_str(), 0);
+ bool exists = file != nullptr;
+ if (file) {
+ zip_fclose(file);
+ }
+ return exists;
+}
+
+
string
-Unzipper::get(string const& filename)
+Unzipper::get(string const& filename) const
{
auto file = zip_fopen(_zip, filename.c_str(), 0);
if (!file) {
diff --git a/src/lib/unzipper.h b/src/lib/unzipper.h
index 7cab6e5f4..76b2fe45a 100644
--- a/src/lib/unzipper.h
+++ b/src/lib/unzipper.h
@@ -33,7 +33,8 @@ public:
Unzipper(Unzipper const&) = delete;
Unzipper& operator=(Unzipper const&) = delete;
- std::string get(std::string const& filename);
+ std::string get(std::string const& filename) const;
+ bool contains(std::string const& filename) const;
private:
struct zip* _zip;
diff --git a/src/lib/util.cc b/src/lib/util.cc
index 2f5c1ce49..282011d65 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -89,10 +89,11 @@ LIBDCP_ENABLE_WARNINGS
#include <dbghelp.h>
#endif
#include <signal.h>
+#include <climits>
#include <iomanip>
#include <iostream>
#include <fstream>
-#include <climits>
+#include <numeric>
#include <stdexcept>
#ifdef DCPOMATIC_POSIX
#include <execinfo.h>
@@ -121,9 +122,6 @@ using std::vector;
using std::wstring;
using boost::thread;
using boost::optional;
-using boost::lexical_cast;
-using boost::bad_lexical_cast;
-using boost::scoped_array;
using dcp::Size;
using dcp::raw_convert;
using dcp::locale_convert;
@@ -1151,6 +1149,7 @@ setup_grok_library_path()
}
#endif
+
string
screen_names_to_string(vector<string> names)
{
@@ -1185,3 +1184,16 @@ report_problem()
return String::compose(_("Please report this problem by using Help -> Report a problem or via email to %1"), variant::report_problem_email());
}
+
+string
+join_strings(vector<string> const& in, string const& separator)
+{
+ if (in.empty()) {
+ return {};
+ }
+
+ return std::accumulate(std::next(in.begin()), in.end(), in.front(), [separator](string a, string b) {
+ return a + separator + b;
+ });
+}
+
diff --git a/src/lib/util.h b/src/lib/util.h
index eac855bef..7c40c5ce8 100644
--- a/src/lib/util.h
+++ b/src/lib/util.h
@@ -101,6 +101,7 @@ extern void capture_ffmpeg_logs();
#ifdef DCPOMATIC_GROK
extern void setup_grok_library_path();
#endif
+extern std::string join_strings(std::vector<std::string> const& in, std::string const& separator = " ");
template <class T>
diff --git a/src/lib/wscript b/src/lib/wscript
index a4758c737..68b988eb3 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -49,7 +49,7 @@ sources = """
text_decoder.cc
case_insensitive_sorter.cc
check_content_job.cc
- cinema.cc
+ cinema_list.cc
cinema_sound_processor.cc
change_signaller.cc
collator.cc
@@ -85,6 +85,7 @@ sources = """
decoder_part.cc
digester.cc
dkdm_recipient.cc
+ dkdm_recipient_list.cc
dkdm_wrapper.cc
dolby_cp750.cc
email.cc
@@ -126,6 +127,7 @@ sources = """
frame_rate_change.cc
guess_crop.cc
hints.cc
+ id.cc
internet.cc
image.cc
image_content.cc
@@ -184,6 +186,9 @@ sources = """
state.cc
spl.cc
spl_entry.cc
+ sqlite_statement.cc
+ sqlite_table.cc
+ sqlite_transaction.cc
string_log_entry.cc
string_text_file.cc
string_text_file_content.cc
@@ -239,7 +244,7 @@ def build(bld):
BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 BOOST_REGEX
SAMPLERATE POSTPROC TIFF SSH DCP CXML GLIB LZMA XML++
CURL ZIP BZ2 FONTCONFIG PANGOMM CAIROMM XMLSEC SUB ICU NETTLE PNG JPEG LEQM_NRT
- LIBZ
+ LIBZ SQLITE3
"""
if bld.env.TARGET_OSX:
diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc
index 1c43d8a07..85fc96c3e 100644
--- a/src/tools/dcpomatic.cc
+++ b/src/tools/dcpomatic.cc
@@ -42,6 +42,7 @@
#include "wx/id.h"
#include "wx/job_manager_view.h"
#include "wx/kdm_dialog.h"
+#include "wx/load_config_from_zip_dialog.h"
#include "wx/nag_dialog.h"
#include "wx/paste_dialog.h"
#include "wx/recreate_chain_dialog.h"
@@ -94,6 +95,7 @@
#include "lib/subtitle_film_encoder.h"
#include "lib/text_content.h"
#include "lib/transcode_job.h"
+#include "lib/unzipper.h"
#include "lib/update_checker.h"
#include "lib/variant.h"
#include "lib/version.h"
@@ -328,7 +330,7 @@ public:
#endif
_config_changed_connection = Config::instance()->Changed.connect(boost::bind(&DOMFrame::config_changed, this, _1));
- config_changed (Config::OTHER);
+ config_changed(Config::OTHER);
_analytics_message_connection = Analytics::instance()->Message.connect(boost::bind(&DOMFrame::analytics_message, this, _1, _2));
@@ -795,9 +797,22 @@ private:
{
FileDialog dialog(this, _("Specify ZIP file"), wxT("ZIP files (*.zip)|*.zip"), wxFD_OPEN, "Preferences");
- if (dialog.show()) {
- Config::instance()->load_from_zip(dialog.path());
+ if (!dialog.show()) {
+ return;
+ }
+
+ auto action = Config::CinemasAction::WRITE_TO_CURRENT_PATH;
+
+ if (Config::zip_contains_cinemas(dialog.path()) && Config::cinemas_file_from_zip(dialog.path()) != Config::instance()->cinemas_file()) {
+ LoadConfigFromZIPDialog how(this, dialog.path());
+ if (how.ShowModal() == wxID_CANCEL) {
+ return;
+ }
+
+ action = how.action();
}
+
+ Config::instance()->load_from_zip(dialog.path(), action);
}
void jobs_make_dcp ()
@@ -1466,48 +1481,19 @@ private:
m->Append (help, _("&Help"));
}
- void config_changed (Config::Property what)
+ void config_changed(Config::Property what)
{
/* Instantly save any config changes when using the DCP-o-matic GUI */
- switch (what) {
- case Config::CINEMAS:
- try {
- Config::instance()->write_cinemas();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to cinemas file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
- break;
- case Config::DKDM_RECIPIENTS:
- try {
- Config::instance()->write_dkdm_recipients();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to DKDM recipients file at %s. Your changes have not been saved."),
- std_to_wx(Config::instance()->dkdm_recipients_file().string()).data()
- )
- );
- }
- break;
- default:
- try {
- Config::instance()->write_config();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format (
- _("Could not write to config file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
+ try {
+ Config::instance()->write_config();
+ } catch (exception& e) {
+ error_dialog (
+ this,
+ wxString::Format (
+ _("Could not write to config file at %s. Your changes have not been saved."),
+ std_to_wx (Config::instance()->cinemas_file().string()).data()
+ )
+ );
}
for (int i = 0; i < _history_items; ++i) {
@@ -1548,6 +1534,8 @@ private:
if (what == Config::GROK) {
setup_grok_library_path();
}
+#else
+ LIBDCP_UNUSED(what);
#endif
}
diff --git a/src/tools/dcpomatic_batch.cc b/src/tools/dcpomatic_batch.cc
index 0a228b933..32e8dec08 100644
--- a/src/tools/dcpomatic_batch.cc
+++ b/src/tools/dcpomatic_batch.cc
@@ -358,36 +358,24 @@ private:
void config_changed (Config::Property what)
{
/* Instantly save any config changes when using the DCP-o-matic GUI */
- if (what == Config::CINEMAS) {
- try {
- Config::instance()->write_cinemas();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format(
- _("Could not write to cinemas file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
- } else {
- try {
- Config::instance()->write_config();
- } catch (exception& e) {
- error_dialog (
- this,
- wxString::Format(
- _("Could not write to config file at %s. Your changes have not been saved."),
- std_to_wx (Config::instance()->cinemas_file().string()).data()
- )
- );
- }
+ try {
+ Config::instance()->write_config();
+ } catch (exception& e) {
+ error_dialog (
+ this,
+ wxString::Format(
+ _("Could not write to config file at %s. Your changes have not been saved."),
+ std_to_wx (Config::instance()->cinemas_file().string()).data()
+ )
+ );
}
#ifdef DCPOMATIC_GROK
if (what == Config::GROK) {
setup_grok_library_path();
}
+#else
+ LIBDCP_UNUSED(what);
#endif
}
diff --git a/src/tools/dcpomatic_kdm.cc b/src/tools/dcpomatic_kdm.cc
index 6f8c3c71e..af16f57aa 100644
--- a/src/tools/dcpomatic_kdm.cc
+++ b/src/tools/dcpomatic_kdm.cc
@@ -405,11 +405,15 @@ private:
return kdm;
};
+ CinemaList cinemas;
+
for (auto i: _screens->screens()) {
auto kdm = kdm_for_screen(
make_kdm,
- i,
+ i.first,
+ *cinemas.cinema(i.first),
+ *cinemas.screen(i.second),
_timing->from(),
_timing->until(),
_output->formulation(),
diff --git a/src/tools/wscript b/src/tools/wscript
index a79995480..e6d7c4be1 100644
--- a/src/tools/wscript
+++ b/src/tools/wscript
@@ -98,7 +98,7 @@ def configure(conf):
def build(bld):
uselib = 'BOOST_THREAD BOOST_DATETIME DCP XMLSEC CXML XMLPP AVFORMAT AVFILTER AVCODEC '
uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB '
- uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG JPEG LEQM_NRT '
+ uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG JPEG LEQM_NRT SQLITE3 '
if bld.env.ENABLE_DISK:
if bld.env.TARGET_LINUX:
diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc
index e6f4eca5e..1a9c564d4 100644
--- a/src/wx/kdm_dialog.cc
+++ b/src/wx/kdm_dialog.cc
@@ -205,8 +205,21 @@ KDMDialog::make_clicked ()
return film->make_kdm(_cpl->cpl(), begin, end);
};
- for (auto i: _screens->screens()) {
- auto p = kdm_for_screen(make_kdm, i, _timing->from(), _timing->until(), _output->formulation(), !_output->forensic_mark_video(), for_audio, period_checks);
+ CinemaList cinemas;
+
+ for (auto screen: _screens->screens()) {
+ auto p = kdm_for_screen(
+ make_kdm,
+ screen.first,
+ *cinemas.cinema(screen.first),
+ *cinemas.screen(screen.second),
+ _timing->from(),
+ _timing->until(),
+ _output->formulation(),
+ !_output->forensic_mark_video(),
+ for_audio,
+ period_checks
+ );
if (p) {
kdms.push_back (p);
}
diff --git a/src/wx/load_config_from_zip_dialog.cc b/src/wx/load_config_from_zip_dialog.cc
new file mode 100644
index 000000000..a7d573ded
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.cc
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2024 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 "load_config_from_zip_dialog.h"
+#include "wx_util.h"
+#include "lib/config.h"
+#include "lib/unzipper.h"
+
+
+LoadConfigFromZIPDialog::LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file)
+ : TableDialog(parent, _("Load configuration from ZIP file"), 1, 0, true)
+{
+ _use_current = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file over the current list at")));
+ auto current_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::instance()->cinemas_file().string())));
+ auto current_path_font = current_path->GetFont();
+ current_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ current_path->SetFont(current_path_font);
+
+ _use_zip = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file to the original location at")));
+ auto zip_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::cinemas_file_from_zip(zip_file).string())));
+ auto zip_path_font = zip_path->GetFont();
+ zip_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ zip_path->SetFont(zip_path_font);
+
+ _ignore = add(new wxRadioButton(this, wxID_ANY, _("Do not use the cinemas in the ZIP file")));
+
+ layout();
+}
+
+
+Config::CinemasAction
+LoadConfigFromZIPDialog::action() const
+{
+ if (_use_current->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_CURRENT_PATH;
+ } else if (_use_zip->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG;
+ }
+
+ return Config::CinemasAction::IGNORE;
+}
diff --git a/src/wx/load_config_from_zip_dialog.h b/src/wx/load_config_from_zip_dialog.h
new file mode 100644
index 000000000..f5f4ec6ea
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2024 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 "table_dialog.h"
+#include "lib/config.h"
+#include <boost/filesystem.hpp>
+
+
+class LoadConfigFromZIPDialog : public TableDialog
+{
+public:
+ LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file);
+
+ Config::CinemasAction action() const;
+
+private:
+ wxRadioButton* _use_current;
+ wxRadioButton* _use_zip;
+ wxRadioButton* _ignore;
+};
+
+
+
diff --git a/src/wx/recipients_panel.cc b/src/wx/recipients_panel.cc
index 04ad0dd6e..9596c3cfd 100644
--- a/src/wx/recipients_panel.cc
+++ b/src/wx/recipients_panel.cc
@@ -23,7 +23,7 @@
#include "wx_util.h"
#include "recipient_dialog.h"
#include "dcpomatic_button.h"
-#include "lib/config.h"
+#include "lib/dkdm_recipient_list.h"
#include <list>
#include <iostream>
@@ -103,15 +103,15 @@ RecipientsPanel::setup_sensitivity ()
void
-RecipientsPanel::add_recipient (shared_ptr<DKDMRecipient> r)
+RecipientsPanel::add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient)
{
string const search = wx_to_std(_search->GetValue());
- if (!search.empty() && !_collator.find(search, r->name)) {
+ if (!search.empty() && !_collator.find(search, recipient.name)) {
return;
}
- _recipients[_targets->AppendItem(_root, std_to_wx(r->name))] = r;
+ _recipients.emplace(make_pair(_targets->AppendItem(_root, std_to_wx(recipient.name)), id));
_targets->SortChildren (_root);
}
@@ -122,9 +122,10 @@ RecipientsPanel::add_recipient_clicked ()
{
RecipientDialog dialog(GetParent(), _("Add recipient"));
if (dialog.ShowModal() == wxID_OK) {
- auto r = std::make_shared<DKDMRecipient>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails());
- Config::instance()->add_dkdm_recipient (r);
- add_recipient (r);
+ auto recipient = DKDMRecipient(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails());
+ DKDMRecipientList recipient_list;
+ auto const id = recipient_list.add_dkdm_recipient(recipient);
+ add_recipient(id, recipient);
}
}
@@ -136,18 +137,28 @@ RecipientsPanel::edit_recipient_clicked ()
return;
}
- auto c = *_selected.begin();
+ DKDMRecipientList recipients;
+ auto selection = *_selected.begin();
+ auto const recipient_id = selection.second;
+ auto recipient = recipients.dkdm_recipient(recipient_id);
+ DCPOMATIC_ASSERT(recipient);
RecipientDialog dialog(
- GetParent(), _("Edit recipient"), c.second->name, c.second->notes, c.second->emails, c.second->recipient
+ GetParent(),
+ _("Edit recipient"),
+ recipient->name,
+ recipient->notes,
+ recipient->emails,
+ recipient->recipient
);
if (dialog.ShowModal() == wxID_OK) {
- c.second->name = dialog.name();
- c.second->emails = dialog.emails();
- c.second->notes = dialog.notes();
- _targets->SetItemText(c.first, std_to_wx(dialog.name()));
- Config::instance()->changed (Config::DKDM_RECIPIENTS);
+ recipient->name = dialog.name();
+ recipient->emails = dialog.emails();
+ recipient->notes = dialog.notes();
+ recipient->recipient = dialog.recipient();
+ recipients.update_dkdm_recipient(recipient_id, *recipient);
+ _targets->SetItemText(selection.first, std_to_wx(dialog.name()));
}
}
@@ -156,7 +167,8 @@ void
RecipientsPanel::remove_recipient_clicked ()
{
for (auto const& i: _selected) {
- Config::instance()->remove_dkdm_recipient (i.second);
+ DKDMRecipientList recipient_list;
+ recipient_list.remove_dkdm_recipient(i.second);
_targets->Delete (i.first);
}
@@ -164,19 +176,15 @@ RecipientsPanel::remove_recipient_clicked ()
}
-list<shared_ptr<DKDMRecipient>>
+list<DKDMRecipient>
RecipientsPanel::recipients () const
{
- list<shared_ptr<DKDMRecipient>> r;
-
- for (auto const& i: _selected) {
- r.push_back (i.second);
+ list<DKDMRecipient> all;
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ all.push_back(recipient.second);
}
-
- r.sort ();
- r.unique ();
-
- return r;
+ return all;
}
@@ -202,7 +210,7 @@ RecipientsPanel::selection_changed ()
for (size_t i = 0; i < s.GetCount(); ++i) {
RecipientMap::const_iterator j = _recipients.find (s[i]);
if (j != _recipients.end ()) {
- _selected[j->first] = j->second;
+ _selected.emplace(*j);
}
}
@@ -216,8 +224,9 @@ RecipientsPanel::add_recipients ()
{
_root = _targets->AddRoot ("Foo");
- for (auto i: Config::instance()->dkdm_recipients()) {
- add_recipient (i);
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ add_recipient(recipient.first, recipient.second);
}
}
diff --git a/src/wx/recipients_panel.h b/src/wx/recipients_panel.h
index 6e1f1408f..d252b8d06 100644
--- a/src/wx/recipients_panel.h
+++ b/src/wx/recipients_panel.h
@@ -21,6 +21,7 @@
#include "lib/collator.h"
#include "lib/dkdm_recipient.h"
+#include "lib/dkdm_recipient_list.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/srchctrl.h>
@@ -43,12 +44,12 @@ public:
void setup_sensitivity ();
- std::list<std::shared_ptr<DKDMRecipient>> recipients () const;
+ std::list<DKDMRecipient> recipients() const;
boost::signals2::signal<void ()> RecipientsChanged;
private:
void add_recipients ();
- void add_recipient (std::shared_ptr<DKDMRecipient>);
+ void add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient);
void add_recipient_clicked ();
void edit_recipient_clicked ();
void remove_recipient_clicked ();
@@ -63,7 +64,7 @@ private:
wxButton* _remove_recipient;
wxTreeItemId _root;
- typedef std::map<wxTreeItemId, std::shared_ptr<DKDMRecipient>> RecipientMap;
+ typedef std::map<wxTreeItemId, DKDMRecipientID> RecipientMap;
RecipientMap _recipients;
RecipientMap _selected;
diff --git a/src/wx/screens_panel.cc b/src/wx/screens_panel.cc
index 768d25092..fdc4dacc3 100644
--- a/src/wx/screens_panel.cc
+++ b/src/wx/screens_panel.cc
@@ -126,8 +126,6 @@ ScreensPanel::ScreensPanel (wxWindow* parent)
_uncheck_all->Bind (wxEVT_BUTTON, boost::bind(&ScreensPanel::uncheck_all, this));
SetSizer(_overall_sizer);
-
- _config_connection = Config::instance()->Changed.connect(boost::bind(&ScreensPanel::config_changed, this, _1));
}
@@ -189,28 +187,37 @@ ScreensPanel::convert_to_lower(string& s)
bool
-ScreensPanel::matches_search(shared_ptr<const Cinema> cinema, string search)
+ScreensPanel::matches_search(Cinema const& cinema, string search)
{
if (search.empty()) {
return true;
}
- return _collator.find(search, cinema->name);
+ return _collator.find(search, cinema.name);
}
+/** Add an existing cinema to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
+ScreensPanel::add_cinema(CinemaID cinema_id, wxTreeListItem previous)
{
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
auto const search = wx_to_std(_search->GetValue());
- if (!matches_search(cinema, search)) {
+ if (!matches_search(*cinema, search)) {
return {};
}
+ auto screens = cinemas.screens(cinema_id);
+
if (_show_only_checked->get()) {
- auto screens = cinema->screens();
- auto iter = std::find_if(screens.begin(), screens.end(), [this](shared_ptr<dcpomatic::Screen> screen) {
- return _checked_screens.find(screen) != _checked_screens.end();
+ auto iter = std::find_if(screens.begin(), screens.end(), [this](pair<ScreenID, dcpomatic::Screen> const& screen) {
+ auto iter2 = std::find_if(_checked_screens.begin(), _checked_screens.end(), [screen](pair<CinemaID, ScreenID> const& checked) {
+ return checked.second == screen.first;
+ });
+ return iter2 != _checked_screens.end();
});
if (iter == screens.end()) {
return {};
@@ -219,29 +226,34 @@ ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
auto id = _targets->InsertItem(_targets->GetRootItem(), previous, std_to_wx(cinema->name));
- _item_to_cinema[id] = cinema;
- _cinema_to_item[cinema] = id;
+ _item_to_cinema.emplace(make_pair(id, cinema_id));
+ _cinema_to_item[cinema_id] = id;
- for (auto screen: cinema->screens()) {
- add_screen (cinema, screen);
+ for (auto screen: screens) {
+ add_screen(cinema_id, screen.first);
}
return id;
}
+/** Add an existing screen to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_screen (shared_ptr<Cinema> cinema, shared_ptr<Screen> screen)
+ScreensPanel::add_screen(CinemaID cinema_id, ScreenID screen_id)
{
- auto item = cinema_to_item(cinema);
+ auto item = cinema_to_item(cinema_id);
if (!item) {
return {};
}
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
auto id = _targets->AppendItem(*item, std_to_wx(screen->name));
- _item_to_screen[id] = screen;
- _screen_to_item[screen] = id;
+ _item_to_screen.emplace(make_pair(id, make_pair(cinema_id, screen_id)));
+ _screen_to_item[screen_id] = id;
return item;
}
@@ -253,44 +265,31 @@ ScreensPanel::add_cinema_clicked ()
CinemaDialog dialog(GetParent(), _("Add Cinema"));
if (dialog.ShowModal() == wxID_OK) {
- auto cinema = make_shared<Cinema>(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset());
+ auto cinema = Cinema(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset());
- auto cinemas = sorted_cinemas();
+ CinemaList cinemas;
+ auto existing_cinemas = cinemas.cinemas();
- try {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- Config::instance()->add_cinema(cinema);
- } catch (FileError& e) {
- error_dialog(
- GetParent(),
- variant::wx::insert_dcpomatic(
- _("Could not write cinema details to the cinemas.xml file. Check that the location of "
- "cinemas.xml is valid in %s's preferences.")
- ),
- std_to_wx(e.what())
- );
- return;
- }
+ auto const cinema_id = cinemas.add_cinema(cinema);
wxTreeListItem previous = wxTLI_FIRST;
bool found = false;
auto const search = wx_to_std(_search->GetValue());
- for (auto existing_cinema: cinemas) {
- if (!matches_search(existing_cinema, search)) {
+ for (auto existing_cinema: existing_cinemas) {
+ if (!matches_search(existing_cinema.second, search)) {
continue;
}
- if (_collator.compare(dialog.name(), existing_cinema->name) < 0) {
+ if (_collator.compare(dialog.name(), existing_cinema.second.name) < 0) {
/* existing_cinema should be after the one we're inserting */
found = true;
break;
}
- auto item = cinema_to_item(existing_cinema);
+ auto item = cinema_to_item(existing_cinema.first);
DCPOMATIC_ASSERT(item);
previous = *item;
}
- auto item = add_cinema(cinema, found ? previous : wxTLI_LAST);
+ auto item = add_cinema(cinema_id, found ? previous : wxTLI_LAST);
if (item) {
_targets->UnselectAll ();
@@ -302,13 +301,13 @@ ScreensPanel::add_cinema_clicked ()
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::cinema_for_operation () const
{
if (_selected_cinemas.size() == 1) {
return _selected_cinemas[0];
} else if (_selected_screens.size() == 1) {
- return _selected_screens[0]->cinema;
+ return _selected_screens[0].first;
}
return {};
@@ -318,25 +317,29 @@ ScreensPanel::cinema_for_operation () const
void
ScreensPanel::edit_cinema_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (cinema) {
- edit_cinema(cinema);
+ auto cinema_id = cinema_for_operation();
+ if (cinema_id) {
+ edit_cinema(*cinema_id);
}
}
void
-ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
+ScreensPanel::edit_cinema(CinemaID cinema_id)
{
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
CinemaDialog dialog(GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset);
if (dialog.ShowModal() == wxID_OK) {
- cinema->name = dialog.name();
cinema->emails = dialog.emails();
+ cinema->name = dialog.name();
cinema->notes = dialog.notes();
cinema->utc_offset = dialog.utc_offset();
- notify_cinemas_changed();
- auto item = cinema_to_item(cinema);
+ cinemas.update_cinema(cinema_id, *cinema);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->SetItemText (*item, std_to_wx(dialog.name()));
}
@@ -346,8 +349,11 @@ ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
void
ScreensPanel::remove_cinema_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_cinemas.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas[0]->name)))) {
+ auto cinema = cinemas.cinema(_selected_cinemas[0]);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(cinema->name)))) {
return;
}
} else {
@@ -358,14 +364,12 @@ ScreensPanel::remove_cinema_clicked ()
auto cinemas_to_remove = _selected_cinemas;
- for (auto const& cinema: cinemas_to_remove) {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- for (auto screen: cinema->screens()) {
- _checked_screens.erase(screen);
+ for (auto const& cinema_id: cinemas_to_remove) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ _checked_screens.erase({cinema_id, screen.first});
}
- Config::instance()->remove_cinema(cinema);
- auto item = cinema_to_item(cinema);
+ cinemas.remove_cinema(cinema_id);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -378,8 +382,8 @@ ScreensPanel::remove_cinema_clicked ()
void
ScreensPanel::add_screen_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (!cinema) {
+ auto cinema_id = cinema_for_operation();
+ if (!cinema_id) {
return;
}
@@ -389,8 +393,10 @@ ScreensPanel::add_screen_clicked ()
return;
}
- for (auto screen: cinema->screens()) {
- if (screen->name == dialog.name()) {
+ CinemaList cinemas;
+
+ for (auto screen: cinemas.screens(*cinema_id)) {
+ if (screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -402,11 +408,10 @@ ScreensPanel::add_screen_clicked ()
}
}
- auto screen = std::make_shared<Screen>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
- cinema->add_screen (screen);
- notify_cinemas_changed();
+ auto screen = Screen(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
+ auto const screen_id = cinemas.add_screen(*cinema_id, screen);
- auto id = add_screen (cinema, screen);
+ auto id = add_screen(*cinema_id, screen_id);
if (id) {
_targets->Expand (id.get ());
}
@@ -417,30 +422,33 @@ void
ScreensPanel::edit_screen_clicked ()
{
if (_selected_screens.size() == 1) {
- edit_screen(_selected_screens[0]);
+ edit_screen(_selected_screens[0].first, _selected_screens[0].second);
}
}
void
-ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
+ScreensPanel::edit_screen(CinemaID cinema_id, ScreenID screen_id)
{
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
ScreenDialog dialog(
GetParent(), _("Edit screen"),
- edit_screen->name,
- edit_screen->notes,
- edit_screen->recipient,
- edit_screen->recipient_file,
- edit_screen->trusted_devices
+ screen->name,
+ screen->notes,
+ screen->recipient,
+ screen->recipient_file,
+ screen->trusted_devices
);
if (dialog.ShowModal() != wxID_OK) {
return;
}
- auto cinema = edit_screen->cinema;
- for (auto screen: cinema->screens()) {
- if (screen != edit_screen && screen->name == dialog.name()) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ if (screen.first != screen_id && screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -452,14 +460,14 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
}
}
- edit_screen->name = dialog.name();
- edit_screen->notes = dialog.notes();
- edit_screen->recipient = dialog.recipient();
- edit_screen->recipient_file = dialog.recipient_file();
- edit_screen->trusted_devices = dialog.trusted_devices();
- notify_cinemas_changed();
+ screen->name = dialog.name();
+ screen->notes = dialog.notes();
+ screen->recipient = dialog.recipient();
+ screen->recipient_file = dialog.recipient_file();
+ screen->trusted_devices = dialog.trusted_devices();
+ cinemas.update_screen(screen_id, *screen);
- auto item = screen_to_item(edit_screen);
+ auto item = screen_to_item(screen_id);
DCPOMATIC_ASSERT (item);
_targets->SetItemText(*item, std_to_wx(dialog.name()));
}
@@ -468,8 +476,12 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
void
ScreensPanel::remove_screen_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_screens.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens[0]->name)))) {
+ auto screen = cinemas.screen(_selected_screens[0].second);
+ DCPOMATIC_ASSERT(screen);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(screen->name)))) {
return;
}
} else {
@@ -478,10 +490,10 @@ ScreensPanel::remove_screen_clicked ()
}
}
- for (auto screen: _selected_screens) {
- _checked_screens.erase(screen);
- screen->cinema->remove_screen(screen);
- auto item = screen_to_item(screen);
+ for (auto screen_id: _selected_screens) {
+ _checked_screens.erase(screen_id);
+ cinemas.remove_screen(screen_id.second);
+ auto item = screen_to_item(screen_id.second);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -490,17 +502,14 @@ ScreensPanel::remove_screen_clicked ()
* as well.
*/
selection_changed();
- notify_cinemas_changed();
setup_show_only_checked();
}
-vector<shared_ptr<Screen>>
+std::set<pair<CinemaID, ScreenID>>
ScreensPanel::screens () const
{
- vector<shared_ptr<Screen>> output;
- std::copy (_checked_screens.begin(), _checked_screens.end(), std::back_inserter(output));
- return output;
+ return _checked_screens;
}
@@ -526,10 +535,10 @@ ScreensPanel::selection_changed ()
for (size_t i = 0; i < selection.size(); ++i) {
if (auto cinema = item_to_cinema(selection[i])) {
- _selected_cinemas.push_back(cinema);
+ _selected_cinemas.push_back(*cinema);
}
if (auto screen = item_to_screen(selection[i])) {
- _selected_screens.push_back(screen);
+ _selected_screens.push_back(*screen);
}
}
@@ -537,24 +546,12 @@ ScreensPanel::selection_changed ()
}
-list<shared_ptr<Cinema>>
-ScreensPanel::sorted_cinemas() const
-{
- auto cinemas = Config::instance()->cinemas();
-
- cinemas.sort(
- [this](shared_ptr<Cinema> a, shared_ptr<Cinema> b) { return _collator.compare(a->name, b->name) < 0; }
- );
-
- return cinemas;
-}
-
-
void
ScreensPanel::add_cinemas ()
{
- for (auto cinema: sorted_cinemas()) {
- add_cinema (cinema, wxTLI_LAST);
+ CinemaList cinemas;
+ for (auto cinema: cinemas.cinemas()) {
+ add_cinema (cinema.first, wxTLI_LAST);
}
}
@@ -588,7 +585,7 @@ ScreensPanel::display_filter_changed()
}
for (auto const& selection: _selected_screens) {
- if (auto item = screen_to_item(selection)) {
+ if (auto item = screen_to_item(selection.second)) {
_targets->Select (*item);
}
}
@@ -598,7 +595,7 @@ ScreensPanel::display_filter_changed()
_ignore_check_change = true;
for (auto const& checked: _checked_screens) {
- if (auto item = screen_to_item(checked)) {
+ if (auto item = screen_to_item(checked.second)) {
_targets->CheckItem(*item, wxCHK_CHECKED);
setup_cinema_checked_state(*item);
}
@@ -614,9 +611,9 @@ ScreensPanel::set_screen_checked (wxTreeListItem item, bool checked)
auto screen = item_to_screen(item);
DCPOMATIC_ASSERT(screen);
if (checked) {
- _checked_screens.insert(screen);
+ _checked_screens.insert({screen->first, screen->second});
} else {
- _checked_screens.erase(screen);
+ _checked_screens.erase({screen->first, screen->second});
}
setup_show_only_checked();
@@ -670,7 +667,7 @@ ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::item_to_cinema (wxTreeListItem item) const
{
auto iter = _item_to_cinema.find (item);
@@ -682,7 +679,7 @@ ScreensPanel::item_to_cinema (wxTreeListItem item) const
}
-shared_ptr<Screen>
+optional<pair<CinemaID, ScreenID>>
ScreensPanel::item_to_screen (wxTreeListItem item) const
{
auto iter = _item_to_screen.find (item);
@@ -695,7 +692,7 @@ ScreensPanel::item_to_screen (wxTreeListItem item) const
optional<wxTreeListItem>
-ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
+ScreensPanel::cinema_to_item(CinemaID cinema) const
{
auto iter = _cinema_to_item.find (cinema);
if (iter == _cinema_to_item.end()) {
@@ -707,7 +704,7 @@ ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
optional<wxTreeListItem>
-ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
+ScreensPanel::screen_to_item(ScreenID screen) const
{
auto iter = _screen_to_item.find (screen);
if (iter == _screen_to_item.end()) {
@@ -718,39 +715,6 @@ ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
}
-bool
-ScreensPanel::notify_cinemas_changed()
-{
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
-
- try {
- Config::instance()->changed(Config::CINEMAS);
- } catch (FileError& e) {
- error_dialog(
- GetParent(),
- variant::wx::insert_dcpomatic(
- _("Could not write cinema details to the cinemas.xml file. Check that the location of "
- "cinemas.xml is valid in %s's preferences.")
- ),
- std_to_wx(e.what())
- );
- return false;
- }
-
- return true;
-}
-
-
-void
-ScreensPanel::config_changed(Config::Property property)
-{
- if (property == Config::Property::CINEMAS && !_ignore_cinemas_changed) {
- clear_and_re_add();
- }
-}
-
-
void
ScreensPanel::item_activated(wxTreeListEvent& ev)
{
@@ -760,7 +724,7 @@ ScreensPanel::item_activated(wxTreeListEvent& ev)
} else {
auto iter = _item_to_screen.find(ev.GetItem());
if (iter != _item_to_screen.end()) {
- edit_screen(iter->second);
+ edit_screen(iter->second.first, iter->second.second);
}
}
}
@@ -783,20 +747,12 @@ ScreensPanel::setup_show_only_checked()
dcp::UTCOffset
ScreensPanel::best_utc_offset() const
{
- auto all_screens = screens();
- if (all_screens.empty()) {
- return {};
- }
-
- dcp::UTCOffset const first = all_screens[0]->cinema->utc_offset;
-
- for (auto screen = std::next(all_screens.begin()); screen != all_screens.end(); ++screen) {
- if ((*screen)->cinema->utc_offset != first) {
- /* Not unique */
- return dcp::UTCOffset();
- }
+ std::set<CinemaID> unique_cinema_ids;
+ for (auto const& screen: screens()) {
+ unique_cinema_ids.insert(screen.first);
}
- return first;
+ CinemaList cinema_list;
+ return cinema_list.unique_utc_offset(unique_cinema_ids).get_value_or(dcp::UTCOffset());
}
diff --git a/src/wx/screens_panel.h b/src/wx/screens_panel.h
index 1e07b6236..98ec2c631 100644
--- a/src/wx/screens_panel.h
+++ b/src/wx/screens_panel.h
@@ -19,6 +19,7 @@
*/
+#include "lib/cinema_list.h"
#include "lib/collator.h"
#include "lib/config.h"
#include <dcp/warnings.h>
@@ -38,7 +39,6 @@ namespace dcpomatic {
}
-class Cinema;
class CheckBox;
@@ -48,7 +48,7 @@ public:
explicit ScreensPanel (wxWindow* parent);
~ScreensPanel ();
- std::vector<std::shared_ptr<dcpomatic::Screen>> screens () const;
+ std::set<std::pair<CinemaID, ScreenID>> screens() const;
void setup_sensitivity ();
dcp::UTCOffset best_utc_offset() const;
@@ -57,38 +57,35 @@ public:
private:
void add_cinemas ();
- boost::optional<wxTreeListItem> add_cinema (std::shared_ptr<Cinema>, wxTreeListItem previous);
- boost::optional<wxTreeListItem> add_screen (std::shared_ptr<Cinema>, std::shared_ptr<dcpomatic::Screen>);
+ boost::optional<wxTreeListItem> add_cinema(CinemaID cinema, wxTreeListItem previous);
+ boost::optional<wxTreeListItem> add_screen(CinemaID cinema, ScreenID screen);
void add_cinema_clicked ();
void edit_cinema_clicked ();
- void edit_cinema(std::shared_ptr<Cinema> cinema);
+ void edit_cinema(CinemaID cinema_id);
void remove_cinema_clicked ();
void add_screen_clicked ();
void edit_screen_clicked ();
- void edit_screen(std::shared_ptr<dcpomatic::Screen> screen);
+ void edit_screen(CinemaID cinema_id, ScreenID screen_id);
void remove_screen_clicked ();
void selection_changed_shim (wxTreeListEvent &);
void selection_changed ();
void display_filter_changed();
void checkbox_changed (wxTreeListEvent& ev);
void item_activated(wxTreeListEvent& ev);
- std::shared_ptr<Cinema> cinema_for_operation () const;
+ boost::optional<CinemaID> cinema_for_operation() const;
void set_screen_checked (wxTreeListItem item, bool checked);
void setup_cinema_checked_state (wxTreeListItem screen);
void check_all ();
void uncheck_all ();
- bool notify_cinemas_changed();
void clear_and_re_add();
- void config_changed(Config::Property);
void convert_to_lower(std::string& s);
- bool matches_search(std::shared_ptr<const Cinema> cinema, std::string search);
- std::list<std::shared_ptr<Cinema>> sorted_cinemas() const;
+ bool matches_search(Cinema const& cinema, std::string search);
void setup_show_only_checked();
- std::shared_ptr<Cinema> item_to_cinema (wxTreeListItem item) const;
- std::shared_ptr<dcpomatic::Screen> item_to_screen (wxTreeListItem item) const;
- boost::optional<wxTreeListItem> cinema_to_item (std::shared_ptr<Cinema> cinema) const;
- boost::optional<wxTreeListItem> screen_to_item (std::shared_ptr<dcpomatic::Screen> screen) const;
+ boost::optional<CinemaID> item_to_cinema(wxTreeListItem item) const;
+ boost::optional<std::pair<CinemaID, ScreenID>> item_to_screen(wxTreeListItem item) const;
+ boost::optional<wxTreeListItem> cinema_to_item(CinemaID cinema) const;
+ boost::optional<wxTreeListItem> screen_to_item(ScreenID screen) const;
wxBoxSizer* _overall_sizer;
wxSearchCtrl* _search;
@@ -106,24 +103,19 @@ private:
/* We want to be able to search (and so remove selected things from the view)
* but not deselect them, so we maintain lists of selected cinemas and screens.
*/
- std::vector<std::shared_ptr<Cinema>> _selected_cinemas;
- std::vector<std::shared_ptr<dcpomatic::Screen>> _selected_screens;
- /* Likewise with checked screens, except that we can work out which cinemas
- * are checked from which screens are checked, so we don't need to store the
- * cinemas.
- */
- std::set<std::shared_ptr<dcpomatic::Screen>> _checked_screens;
+ std::vector<CinemaID> _selected_cinemas;
+ /* List of cinema_id, screen_id */
+ std::vector<std::pair<CinemaID, ScreenID>> _selected_screens;
+ /* Likewise with checked screens */
+ std::set<std::pair<CinemaID, ScreenID>> _checked_screens;
- std::map<wxTreeListItem, std::shared_ptr<Cinema>> _item_to_cinema;
- std::map<wxTreeListItem, std::shared_ptr<dcpomatic::Screen>> _item_to_screen;
- std::map<std::shared_ptr<Cinema>, wxTreeListItem> _cinema_to_item;
- std::map<std::shared_ptr<dcpomatic::Screen>, wxTreeListItem> _screen_to_item;
+ std::map<wxTreeListItem, CinemaID> _item_to_cinema;
+ std::map<wxTreeListItem, std::pair<CinemaID, ScreenID>> _item_to_screen;
+ std::map<CinemaID, wxTreeListItem> _cinema_to_item;
+ std::map<ScreenID, wxTreeListItem> _screen_to_item;
bool _ignore_selection_change = false;
bool _ignore_check_change = false;
Collator _collator;
-
- boost::signals2::scoped_connection _config_connection;
- bool _ignore_cinemas_changed = false;
};
diff --git a/src/wx/wscript b/src/wx/wscript
index b37d26d6d..a41e3827e 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -113,6 +113,7 @@ sources = """
language_subtag_panel.cc
language_tag_dialog.cc
language_tag_widget.cc
+ load_config_from_zip_dialog.cc
kdm_choice.cc
make_chain_dialog.cc
markers.cc
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index 7d8fb0a76..1c04a4755 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -810,20 +810,6 @@ report_config_load_failure(wxWindow* parent, Config::LoadFailure what)
case Config::LoadFailure::CONFIG:
message_dialog(parent, _("The existing configuration failed to load. Default values will be used instead. These may take a short time to create."));
break;
- case Config::LoadFailure::CINEMAS:
- message_dialog(
- parent,
- _(wxString::Format("The cinemas list for creating KDMs (cinemas.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->cinemas_file().parent_path().string())))
- );
- break;
- case Config::LoadFailure::DKDM_RECIPIENTS:
- message_dialog(
- parent,
- _(wxString::Format("The recipients list for creating DKDMs (dkdm_recipients.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->dkdm_recipients_file().parent_path().string())))
- );
- break;
}
}