94e83f8077de30baf735722c2dd696a4c2e3d608
[dcpomatic.git] / src / lib / cinema_kdms.cc
1 /*
2     Copyright (C) 2013-2015 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 #include "exceptions.h"
22 #include "cinema_kdms.h"
23 #include "cinema.h"
24 #include "screen.h"
25 #include "config.h"
26 #include "util.h"
27 #include "emailer.h"
28 #include "compose.hpp"
29 #include "log.h"
30 #include "zipper.h"
31 #include "dcpomatic_log.h"
32 #include "kdm_with_metadata.h"
33 #include <boost/foreach.hpp>
34
35 #include "i18n.h"
36
37 using std::list;
38 using std::cout;
39 using std::string;
40 using std::runtime_error;
41 using boost::shared_ptr;
42 using boost::function;
43 using boost::optional;
44
45 void
46 make_zip_file (list<KDMWithMetadataPtr> kdms, boost::filesystem::path zip_file, dcp::NameFormat name_format, dcp::NameFormat::Map name_values)
47 {
48         Zipper zipper (zip_file);
49
50         BOOST_FOREACH (KDMWithMetadataPtr i, kdms) {
51                 name_values['i'] = i->kdm_id ();
52                 string const name = careful_string_filter(name_format.get(name_values, ".xml"));
53                 zipper.add (name, i->kdm_as_xml());
54         }
55
56         zipper.close ();
57 }
58
59 /** Collect a list of KDMWithMetadatas into a list of list<KDMWithMetadata> so that each
60  *  CinemaKDM contains the KDMs for its cinema.
61  */
62 list<list<KDMWithMetadataPtr> >
63 collect (list<KDMWithMetadataPtr> screen_kdms)
64 {
65         list<list<KDMWithMetadataPtr> > cinema_kdms;
66
67         while (!screen_kdms.empty ()) {
68
69                 /* Get all the screens from a single cinema */
70
71                 list<KDMWithMetadataPtr> ck;
72
73                 list<KDMWithMetadataPtr>::iterator i = screen_kdms.begin ();
74                 ck.push_back (*i);
75                 list<KDMWithMetadataPtr>::iterator j = i;
76                 ++i;
77                 screen_kdms.remove (*j);
78
79                 while (i != screen_kdms.end ()) {
80                         if ((*i)->cinema() == ck.front()->cinema()) {
81                                 ck.push_back (*i);
82                                 list<KDMWithMetadataPtr>::iterator j = i;
83                                 ++i;
84                                 screen_kdms.remove (*j);
85                         } else {
86                                 ++i;
87                         }
88                 }
89
90                 cinema_kdms.push_back (ck);
91         }
92
93         return cinema_kdms;
94 }
95
96 /** Write one directory per cinema into another directory */
97 int
98 write_directories (
99         list<list<KDMWithMetadataPtr> > cinema_kdms,
100         boost::filesystem::path directory,
101         dcp::NameFormat container_name_format,
102         dcp::NameFormat filename_format,
103         dcp::NameFormat::Map name_values,
104         function<bool (boost::filesystem::path)> confirm_overwrite
105         )
106 {
107         /* No specific screen */
108         name_values['s'] = "";
109
110         int written = 0;
111
112         BOOST_FOREACH (list<KDMWithMetadataPtr> const & i, cinema_kdms) {
113                 boost::filesystem::path path = directory;
114                 path /= container_name_format.get(name_values, "");
115                 if (!boost::filesystem::exists (path) || confirm_overwrite (path)) {
116                         boost::filesystem::create_directories (path);
117                         write_files (i, path, filename_format, name_values, confirm_overwrite);
118                 }
119                 written += i.size();
120         }
121
122         return written;
123 }
124
125 /** Write one ZIP file per cinema into a directory */
126 int
127 write_zip_files (
128         list<list<KDMWithMetadataPtr> > cinema_kdms,
129         boost::filesystem::path directory,
130         dcp::NameFormat container_name_format,
131         dcp::NameFormat filename_format,
132         dcp::NameFormat::Map name_values,
133         function<bool (boost::filesystem::path)> confirm_overwrite
134         )
135 {
136         /* No specific screen */
137         name_values['s'] = "";
138
139         int written = 0;
140
141         BOOST_FOREACH (list<KDMWithMetadataPtr> const & i, cinema_kdms) {
142                 boost::filesystem::path path = directory;
143                 path /= container_name_format.get(name_values, ".zip");
144                 if (!boost::filesystem::exists (path) || confirm_overwrite (path)) {
145                         if (boost::filesystem::exists (path)) {
146                                 /* Creating a new zip file over an existing one is an error */
147                                 boost::filesystem::remove (path);
148                         }
149                         make_zip_file (i, path, filename_format, name_values);
150                         written += i.size();
151                 }
152         }
153
154         return written;
155 }
156
157 /** Email one ZIP file per cinema to the cinema.
158  *  @param cinema_kdms KDMS to email.
159  *  @param container_name_format Format of folder / ZIP to use.
160  *  @param filename_format Format of filenames to use.
161  *  @param name_values Values to substitute into \p container_name_format and \p filename_format.
162  *  @param cpl_name Name of the CPL that the KDMs are for.
163  */
164 void
165 email (
166         list<list<KDMWithMetadataPtr> > cinema_kdms,
167         dcp::NameFormat container_name_format,
168         dcp::NameFormat filename_format,
169         dcp::NameFormat::Map name_values,
170         string cpl_name
171         )
172 {
173         Config* config = Config::instance ();
174
175         if (config->mail_server().empty()) {
176                 throw NetworkError (_("No mail server configured in preferences"));
177         }
178
179         /* No specific screen */
180         name_values['s'] = "";
181
182         BOOST_FOREACH (list<KDMWithMetadataPtr> const & i, cinema_kdms) {
183
184                 if (i.front()->cinema()->emails.empty()) {
185                         continue;
186                 }
187
188                 boost::filesystem::path zip_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
189                 boost::filesystem::create_directories (zip_file);
190                 zip_file /= container_name_format.get(name_values, ".zip");
191                 make_zip_file (i, zip_file, filename_format, name_values);
192
193                 string subject = config->kdm_subject();
194                 boost::algorithm::replace_all (subject, "$CPL_NAME", cpl_name);
195                 boost::algorithm::replace_all (subject, "$START_TIME", name_values['b']);
196                 boost::algorithm::replace_all (subject, "$END_TIME", name_values['e']);
197                 boost::algorithm::replace_all (subject, "$CINEMA_NAME", i.front()->cinema()->name);
198
199                 string body = config->kdm_email().c_str();
200                 boost::algorithm::replace_all (body, "$CPL_NAME", cpl_name);
201                 boost::algorithm::replace_all (body, "$START_TIME", name_values['b']);
202                 boost::algorithm::replace_all (body, "$END_TIME", name_values['e']);
203                 boost::algorithm::replace_all (body, "$CINEMA_NAME", i.front()->cinema()->name);
204
205                 string screens;
206                 BOOST_FOREACH (KDMWithMetadataPtr j, i) {
207                         optional<string> screen_name = j->get('n');
208                         if (screen_name) {
209                                 screens += *screen_name + ", ";
210                         }
211                 }
212                 boost::algorithm::replace_all (body, "$SCREENS", screens.substr (0, screens.length() - 2));
213
214                 Emailer email (config->kdm_from(), i.front()->cinema()->emails, subject, body);
215
216                 BOOST_FOREACH (string i, config->kdm_cc()) {
217                         email.add_cc (i);
218                 }
219                 if (!config->kdm_bcc().empty ()) {
220                         email.add_bcc (config->kdm_bcc ());
221                 }
222
223                 email.add_attachment (zip_file, container_name_format.get(name_values, ".zip"), "application/zip");
224
225                 Config* c = Config::instance ();
226
227                 try {
228                         email.send (c->mail_server(), c->mail_port(), c->mail_protocol(), c->mail_user(), c->mail_password());
229                 } catch (...) {
230                         boost::filesystem::remove (zip_file);
231                         dcpomatic_log->log ("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
232                         dcpomatic_log->log (email.email(), LogEntry::TYPE_DEBUG_EMAIL);
233                         dcpomatic_log->log ("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
234                         dcpomatic_log->log (email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
235                         throw;
236                 }
237
238                 boost::filesystem::remove (zip_file);
239
240                 dcpomatic_log->log ("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
241                 dcpomatic_log->log (email.email(), LogEntry::TYPE_DEBUG_EMAIL);
242                 dcpomatic_log->log ("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
243                 dcpomatic_log->log (email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
244         }
245 }