Missed update to private test repo version.
[dcpomatic.git] / src / lib / kdm_with_metadata.cc
1 /*
2     Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "cinema.h"
23 #include "config.h"
24 #include "cross.h"
25 #include "dcpomatic_log.h"
26 #include "email.h"
27 #include "kdm_with_metadata.h"
28 #include "screen.h"
29 #include "util.h"
30 #include "zipper.h"
31 #include <dcp/file.h>
32 #include <dcp/filesystem.h>
33
34 #include "i18n.h"
35
36
37 using std::cout;
38 using std::function;
39 using std::list;
40 using std::shared_ptr;
41 using std::string;
42 using std::vector;
43 using boost::optional;
44
45
46 int
47 write_files (
48         list<KDMWithMetadataPtr> kdms,
49         boost::filesystem::path directory,
50         dcp::NameFormat name_format,
51         std::function<bool (boost::filesystem::path)> confirm_overwrite
52         )
53 {
54         int written = 0;
55
56         if (directory == "-") {
57                 /* Write KDMs to the stdout */
58                 for (auto i: kdms) {
59                         cout << i->kdm_as_xml ();
60                         ++written;
61                 }
62
63                 return written;
64         }
65
66         if (!dcp::filesystem::exists(directory)) {
67                 dcp::filesystem::create_directories(directory);
68         }
69
70         /* Write KDMs to the specified directory */
71         for (auto i: kdms) {
72                 auto out = directory / careful_string_filter(name_format.get(i->name_values(), ".xml"));
73                 if (!dcp::filesystem::exists(out) || confirm_overwrite(out)) {
74                         i->kdm_as_xml (out);
75                         ++written;
76                 }
77         }
78
79         return written;
80 }
81
82
83 optional<string>
84 KDMWithMetadata::get (char k) const
85 {
86         auto i = _name_values.find (k);
87         if (i == _name_values.end()) {
88                 return {};
89         }
90
91         return i->second;
92 }
93
94
95 void
96 make_zip_file (list<KDMWithMetadataPtr> kdms, boost::filesystem::path zip_file, dcp::NameFormat name_format)
97 {
98         Zipper zipper (zip_file);
99
100         for (auto i: kdms) {
101                 auto const name = careful_string_filter(name_format.get(i->name_values(), ".xml"));
102                 zipper.add (name, i->kdm_as_xml());
103         }
104
105         zipper.close ();
106 }
107
108
109 /** Collect a list of KDMWithMetadatas into a list of lists so that
110  *  each list contains the KDMs for one list.
111  */
112 list<list<KDMWithMetadataPtr>>
113 collect (list<KDMWithMetadataPtr> kdms)
114 {
115         list<list<KDMWithMetadataPtr>> grouped;
116
117         for (auto i: kdms) {
118
119                 auto j = grouped.begin ();
120
121                 while (j != grouped.end()) {
122                         if (j->front()->group() == i->group()) {
123                                 j->push_back (i);
124                                 break;
125                         }
126                         ++j;
127                 }
128
129                 if (j == grouped.end()) {
130                         grouped.push_back (list<KDMWithMetadataPtr>());
131                         grouped.back().push_back (i);
132                 }
133         }
134
135         return grouped;
136 }
137
138
139 /** Write one directory per list into another directory */
140 int
141 write_directories (
142         list<list<KDMWithMetadataPtr>> kdms,
143         boost::filesystem::path directory,
144         dcp::NameFormat container_name_format,
145         dcp::NameFormat filename_format,
146         function<bool (boost::filesystem::path)> confirm_overwrite
147         )
148 {
149         int written = 0;
150
151         for (auto const& kdm: kdms) {
152                 auto path = directory;
153                 path /= container_name_format.get(kdm.front()->name_values(), "", "s");
154                 if (!dcp::filesystem::exists(path) || confirm_overwrite(path)) {
155                         dcp::filesystem::create_directories(path);
156                         write_files(kdm, path, filename_format, confirm_overwrite);
157                         written += kdm.size();
158                 }
159         }
160
161         return written;
162 }
163
164
165 /** Write one ZIP file per cinema into a directory */
166 int
167 write_zip_files (
168         list<list<KDMWithMetadataPtr>> kdms,
169         boost::filesystem::path directory,
170         dcp::NameFormat container_name_format,
171         dcp::NameFormat filename_format,
172         function<bool (boost::filesystem::path)> confirm_overwrite
173         )
174 {
175         int written = 0;
176
177         for (auto const& kdm: kdms) {
178                 auto path = directory;
179                 path /= container_name_format.get(kdm.front()->name_values(), ".zip", "s");
180                 if (!dcp::filesystem::exists(path) || confirm_overwrite(path)) {
181                         if (dcp::filesystem::exists(path)) {
182                                 /* Creating a new zip file over an existing one is an error */
183                                 dcp::filesystem::remove(path);
184                         }
185                         make_zip_file(kdm, path, filename_format);
186                         written += kdm.size();
187                 }
188         }
189
190         return written;
191 }
192
193
194 /** Email one ZIP file per cinema to the cinema.
195  *  @param kdms KDMs to email.
196  *  @param container_name_format Format of folder / ZIP to use.
197  *  @param filename_format Format of filenames to use.
198  *  @param name_values Values to substitute into \p container_name_format and \p filename_format.
199  *  @param cpl_name Name of the CPL that the KDMs are for.
200  */
201 void
202 send_emails (
203         list<list<KDMWithMetadataPtr>> kdms,
204         dcp::NameFormat container_name_format,
205         dcp::NameFormat filename_format,
206         string cpl_name,
207         vector<string> extra_addresses
208         )
209 {
210         auto config = Config::instance ();
211
212         if (config->mail_server().empty()) {
213                 throw MissingConfigurationError(_("No outgoing mail server configured in the Email tab of preferences"));
214         }
215
216         if (config->kdm_from().empty()) {
217                 throw MissingConfigurationError(_("No from address configured in the KDM Email tab of preferences"));
218         }
219
220         for (auto const& kdms_for_cinema: kdms) {
221
222                 auto first = kdms_for_cinema.front();
223
224                 auto zip_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
225                 dcp::filesystem::create_directories(zip_file);
226                 zip_file /= container_name_format.get(first->name_values(), ".zip");
227                 make_zip_file (kdms_for_cinema, zip_file, filename_format);
228
229                 auto substitute_variables = [cpl_name, first](string target) {
230                         boost::algorithm::replace_all(target, "$CPL_NAME", cpl_name);
231                         boost::algorithm::replace_all(target, "$START_TIME", first->get('b').get_value_or(""));
232                         boost::algorithm::replace_all(target, "$END_TIME", first->get('e').get_value_or(""));
233                         boost::algorithm::replace_all(target, "$CINEMA_NAME", first->get('c').get_value_or(""));
234                         boost::algorithm::replace_all(target, "$CINEMA_SHORT_NAME", first->get('c').get_value_or("").substr(0, 14));
235                         return target;
236                 };
237
238                 auto subject = substitute_variables(config->kdm_subject());
239                 auto body = substitute_variables(config->kdm_email());
240
241                 vector<string> screens;
242                 for (auto kdm: kdms_for_cinema) {
243                         if (auto screen_name = kdm->get('s')) {
244                                 screens.push_back(*screen_name);
245                         }
246                 }
247                 boost::algorithm::replace_all(body, "$SCREENS", screen_names_to_string(screens));
248
249                 auto emails = first->emails();
250                 std::copy(extra_addresses.begin(), extra_addresses.end(), std::back_inserter(emails));
251                 if (emails.empty()) {
252                         continue;
253                 }
254
255                 Email email(config->kdm_from(), { emails.front() }, subject, body);
256
257                 /* Use CC for the second and subsequent email addresses, so we seem less spammy (#2310) */
258                 for (auto cc = std::next(emails.begin()); cc != emails.end(); ++cc) {
259                         email.add_cc(*cc);
260                 }
261
262                 for (auto cc: config->kdm_cc()) {
263                         email.add_cc (cc);
264                 }
265                 if (!config->kdm_bcc().empty()) {
266                         email.add_bcc (config->kdm_bcc());
267                 }
268
269                 email.add_attachment (zip_file, container_name_format.get(first->name_values(), ".zip"), "application/zip");
270                 dcp::filesystem::remove(zip_file);
271
272                 auto log_details = [](Email& email) {
273                         dcpomatic_log->log("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
274                         dcpomatic_log->log(email.email(), LogEntry::TYPE_DEBUG_EMAIL);
275                         dcpomatic_log->log("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
276                         dcpomatic_log->log(email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
277                 };
278
279                 try {
280                         email.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
281                 } catch (...) {
282                         log_details (email);
283                         throw;
284                 }
285
286                 log_details (email);
287         }
288 }