bebb5df86d0b44f6af12a2c5f371d2e2620eed6e
[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 "emailer.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
33 #include "i18n.h"
34
35
36 using std::cout;
37 using std::function;
38 using std::list;
39 using std::shared_ptr;
40 using std::string;
41 using std::vector;
42 using boost::optional;
43
44
45 int
46 write_files (
47         list<KDMWithMetadataPtr> kdms,
48         boost::filesystem::path directory,
49         dcp::NameFormat name_format,
50         std::function<bool (boost::filesystem::path)> confirm_overwrite
51         )
52 {
53         int written = 0;
54
55         if (directory == "-") {
56                 /* Write KDMs to the stdout */
57                 for (auto i: kdms) {
58                         cout << i->kdm_as_xml ();
59                         ++written;
60                 }
61
62                 return written;
63         }
64
65         if (!boost::filesystem::exists (directory)) {
66                 boost::filesystem::create_directories (directory);
67         }
68
69         /* Write KDMs to the specified directory */
70         for (auto i: kdms) {
71                 auto out = dcp::fix_long_path(directory / careful_string_filter(name_format.get(i->name_values(), ".xml")));
72                 if (!boost::filesystem::exists (out) || confirm_overwrite (out)) {
73                         i->kdm_as_xml (out);
74                         ++written;
75                 }
76         }
77
78         return written;
79 }
80
81
82 optional<string>
83 KDMWithMetadata::get (char k) const
84 {
85         auto i = _name_values.find (k);
86         if (i == _name_values.end()) {
87                 return {};
88         }
89
90         return i->second;
91 }
92
93
94 void
95 make_zip_file (list<KDMWithMetadataPtr> kdms, boost::filesystem::path zip_file, dcp::NameFormat name_format)
96 {
97         Zipper zipper (zip_file);
98
99         for (auto i: kdms) {
100                 auto const name = careful_string_filter(name_format.get(i->name_values(), ".xml"));
101                 zipper.add (name, i->kdm_as_xml());
102         }
103
104         zipper.close ();
105 }
106
107
108 /** Collect a list of KDMWithMetadatas into a list of lists so that
109  *  each list contains the KDMs for one list.
110  */
111 list<list<KDMWithMetadataPtr>>
112 collect (list<KDMWithMetadataPtr> kdms)
113 {
114         list<list<KDMWithMetadataPtr>> grouped;
115
116         for (auto i: kdms) {
117
118                 auto j = grouped.begin ();
119
120                 while (j != grouped.end()) {
121                         if (j->front()->group() == i->group()) {
122                                 j->push_back (i);
123                                 break;
124                         }
125                         ++j;
126                 }
127
128                 if (j == grouped.end()) {
129                         grouped.push_back (list<KDMWithMetadataPtr>());
130                         grouped.back().push_back (i);
131                 }
132         }
133
134         return grouped;
135 }
136
137
138 /** Write one directory per list into another directory */
139 int
140 write_directories (
141         list<list<KDMWithMetadataPtr>> kdms,
142         boost::filesystem::path directory,
143         dcp::NameFormat container_name_format,
144         dcp::NameFormat filename_format,
145         function<bool (boost::filesystem::path)> confirm_overwrite
146         )
147 {
148         int written = 0;
149
150         for (auto const& kdm: kdms) {
151                 auto path = directory;
152                 path /= container_name_format.get(kdm.front()->name_values(), "", "s");
153                 if (!boost::filesystem::exists (path) || confirm_overwrite (path)) {
154                         boost::filesystem::create_directories (path);
155                         write_files(kdm, path, filename_format, confirm_overwrite);
156                 }
157                 written += kdm.size();
158         }
159
160         return written;
161 }
162
163
164 /** Write one ZIP file per cinema into a directory */
165 int
166 write_zip_files (
167         list<list<KDMWithMetadataPtr>> kdms,
168         boost::filesystem::path directory,
169         dcp::NameFormat container_name_format,
170         dcp::NameFormat filename_format,
171         function<bool (boost::filesystem::path)> confirm_overwrite
172         )
173 {
174         int written = 0;
175
176         for (auto const& kdm: kdms) {
177                 auto path = directory;
178                 path /= container_name_format.get(kdm.front()->name_values(), ".zip", "s");
179                 if (!boost::filesystem::exists (path) || confirm_overwrite (path)) {
180                         if (boost::filesystem::exists (path)) {
181                                 /* Creating a new zip file over an existing one is an error */
182                                 boost::filesystem::remove (path);
183                         }
184                         make_zip_file(kdm, path, filename_format);
185                         written += kdm.size();
186                 }
187         }
188
189         return written;
190 }
191
192
193 /** Email one ZIP file per cinema to the cinema.
194  *  @param kdms KDMs to email.
195  *  @param container_name_format Format of folder / ZIP to use.
196  *  @param filename_format Format of filenames to use.
197  *  @param name_values Values to substitute into \p container_name_format and \p filename_format.
198  *  @param cpl_name Name of the CPL that the KDMs are for.
199  */
200 void
201 send_emails (
202         list<list<KDMWithMetadataPtr>> kdms,
203         dcp::NameFormat container_name_format,
204         dcp::NameFormat filename_format,
205         string cpl_name,
206         vector<string> extra_addresses
207         )
208 {
209         auto config = Config::instance ();
210
211         if (config->mail_server().empty()) {
212                 throw NetworkError (_("No mail server configured in preferences"));
213         }
214
215         if (config->kdm_from().empty()) {
216                 throw NetworkError(_("No KDM from address configured in preferences"));
217         }
218
219         for (auto const& kdms_for_cinema: kdms) {
220
221                 auto first = kdms_for_cinema.front();
222
223                 if (first->emails().empty()) {
224                         continue;
225                 }
226
227                 auto zip_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
228                 boost::filesystem::create_directories (zip_file);
229                 zip_file /= container_name_format.get(first->name_values(), ".zip");
230                 make_zip_file (kdms_for_cinema, zip_file, filename_format);
231
232                 auto substitute_variables = [cpl_name, first](string target) {
233                         boost::algorithm::replace_all(target, "$CPL_NAME", cpl_name);
234                         boost::algorithm::replace_all(target, "$START_TIME", first->get('b').get_value_or(""));
235                         boost::algorithm::replace_all(target, "$END_TIME", first->get('e').get_value_or(""));
236                         boost::algorithm::replace_all(target, "$CINEMA_NAME", first->get('c').get_value_or(""));
237                         boost::algorithm::replace_all(target, "$CINEMA_SHORT_NAME", first->get('c').get_value_or("").substr(0, 14));
238                         return target;
239                 };
240
241                 auto subject = substitute_variables(config->kdm_subject());
242                 auto body = substitute_variables(config->kdm_email());
243
244                 string screens;
245                 for (auto kdm: kdms_for_cinema) {
246                         auto screen_name = kdm->get('s');
247                         if (screen_name) {
248                                 screens += *screen_name + ", ";
249                         }
250                 }
251                 boost::algorithm::replace_all (body, "$SCREENS", screens.substr (0, screens.length() - 2));
252
253                 Emailer email (config->kdm_from(), first->emails(), subject, body);
254
255                 for (auto cc: config->kdm_cc()) {
256                         email.add_cc (cc);
257                 }
258                 if (!config->kdm_bcc().empty()) {
259                         email.add_bcc (config->kdm_bcc());
260                 }
261
262                 email.add_attachment (zip_file, container_name_format.get(first->name_values(), ".zip"), "application/zip");
263
264                 auto log_details = [](Emailer& email) {
265                         dcpomatic_log->log("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
266                         dcpomatic_log->log(email.email(), LogEntry::TYPE_DEBUG_EMAIL);
267                         dcpomatic_log->log("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
268                         dcpomatic_log->log(email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
269                 };
270
271                 try {
272                         email.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
273                 } catch (...) {
274                         boost::filesystem::remove (zip_file);
275                         log_details (email);
276                         throw;
277                 }
278
279                 log_details (email);
280
281                 for (auto extra: extra_addresses) {
282                         Emailer email (config->kdm_from(), { extra }, subject, body);
283                         email.add_attachment (zip_file, container_name_format.get(first->name_values(), ".zip"), "application/zip");
284
285                         try {
286                                 email.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
287                         } catch (...) {
288                                 boost::filesystem::remove (zip_file);
289                                 log_details (email);
290                                 throw;
291                         }
292
293                         log_details (email);
294                 }
295
296                 boost::filesystem::remove (zip_file);
297         }
298 }