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