Merge master.
[dcpomatic.git] / src / lib / kdm.cc
1 /*
2     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <list>
21 #include <boost/shared_ptr.hpp>
22 #include <quickmail.h>
23 #include <zip.h>
24 #include <dcp/encrypted_kdm.h>
25 #include <dcp/types.h>
26 #include "kdm.h"
27 #include "cinema.h"
28 #include "exceptions.h"
29 #include "util.h"
30 #include "film.h"
31 #include "config.h"
32
33 using std::list;
34 using std::string;
35 using std::stringstream;
36 using std::cout;
37 using boost::shared_ptr;
38
39 struct ScreenKDM
40 {
41         ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
42                 : screen (s)
43                 , kdm (k)
44         {}
45         
46         shared_ptr<Screen> screen;
47         dcp::EncryptedKDM kdm;
48 };
49
50 static string
51 kdm_filename (shared_ptr<const Film> film, ScreenKDM kdm)
52 {
53         return tidy_for_filename (film->name()) + "_" + tidy_for_filename (kdm.screen->cinema->name) + "_" + tidy_for_filename (kdm.screen->name) + ".kdm.xml";
54 }
55
56 struct CinemaKDMs
57 {
58         shared_ptr<Cinema> cinema;
59         list<ScreenKDM> screen_kdms;
60
61         void make_zip_file (shared_ptr<const Film> film, boost::filesystem::path zip_file) const
62         {
63                 int error;
64                 struct zip* zip = zip_open (zip_file.string().c_str(), ZIP_CREATE | ZIP_EXCL, &error);
65                 if (!zip) {
66                         if (error == ZIP_ER_EXISTS) {
67                                 throw FileError ("ZIP file already exists", zip_file);
68                         }
69                         throw FileError ("could not create ZIP file", zip_file);
70                 }
71                 
72                 list<shared_ptr<string> > kdm_strings;
73                 
74                 for (list<ScreenKDM>::const_iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
75                         shared_ptr<string> kdm (new string (i->kdm.as_xml ()));
76                         kdm_strings.push_back (kdm);
77                         
78                         struct zip_source* source = zip_source_buffer (zip, kdm->c_str(), kdm->length(), 0);
79                         if (!source) {
80                                 throw StringError ("could not create ZIP source");
81                         }
82                         
83                         if (zip_add (zip, kdm_filename (film, *i).c_str(), source) == -1) {
84                                 throw StringError ("failed to add KDM to ZIP archive");
85                         }
86                 }
87                 
88                 if (zip_close (zip) == -1) {
89                         throw StringError ("failed to close ZIP archive");
90                 }
91         }
92 };
93
94 /* Not complete but sufficient for our purposes (we're using
95    ScreenKDM in a list where all the screens will be unique).
96 */
97 bool
98 operator== (ScreenKDM const & a, ScreenKDM const & b)
99 {
100         return a.screen == b.screen;
101 }
102
103 static list<ScreenKDM>
104 make_screen_kdms (
105         shared_ptr<const Film> film,
106         list<shared_ptr<Screen> > screens,
107         boost::filesystem::path cpl,
108         dcp::LocalTime from,
109         dcp::LocalTime to,
110         dcp::Formulation formulation
111         )
112 {
113         list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, cpl, from, to, formulation);
114            
115         list<ScreenKDM> screen_kdms;
116         
117         list<shared_ptr<Screen> >::iterator i = screens.begin ();
118         list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
119         while (i != screens.end() && j != kdms.end ()) {
120                 screen_kdms.push_back (ScreenKDM (*i, *j));
121                 ++i;
122                 ++j;
123         }
124
125         return screen_kdms;
126 }
127
128 static list<CinemaKDMs>
129 make_cinema_kdms (
130         shared_ptr<const Film> film,
131         list<shared_ptr<Screen> > screens,
132         boost::filesystem::path cpl,
133         dcp::LocalTime from,
134         dcp::LocalTime to,
135         dcp::Formulation formulation
136         )
137 {
138         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to, formulation);
139         list<CinemaKDMs> cinema_kdms;
140
141         while (!screen_kdms.empty ()) {
142                 
143                 /* Get all the screens from a single cinema */
144
145                 CinemaKDMs ck;
146                 
147                 list<ScreenKDM>::iterator i = screen_kdms.begin ();
148                 ck.cinema = i->screen->cinema;
149                 ck.screen_kdms.push_back (*i);
150                 list<ScreenKDM>::iterator j = i;
151                 ++i;
152                 screen_kdms.remove (*j);
153                 
154                 while (i != screen_kdms.end ()) {
155                         if (i->screen->cinema == ck.cinema) {
156                                 ck.screen_kdms.push_back (*i);
157                                 list<ScreenKDM>::iterator j = i;
158                                 ++i;
159                                 screen_kdms.remove (*j);
160                         } else {
161                                 ++i;
162                         }
163                 }
164
165                 cinema_kdms.push_back (ck);
166         }
167
168         return cinema_kdms;
169 }
170
171 /** @param from KDM from time in local time.
172  *  @param to KDM to time in local time.
173  */
174 void
175 write_kdm_files (
176         shared_ptr<const Film> film,
177         list<shared_ptr<Screen> > screens,
178         boost::filesystem::path cpl,
179         dcp::LocalTime from,
180         dcp::LocalTime to,
181         dcp::Formulation formulation,
182         boost::filesystem::path directory
183         )
184 {
185         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, cpl, from, to, formulation);
186
187         /* Write KDMs to the specified directory */
188         for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
189                 boost::filesystem::path out = directory;
190                 out /= kdm_filename (film, *i);
191                 i->kdm.as_xml (out);
192         }
193 }
194
195 void
196 write_kdm_zip_files (
197         shared_ptr<const Film> film,
198         list<shared_ptr<Screen> > screens,
199         boost::filesystem::path cpl,
200         dcp::LocalTime from,
201         dcp::LocalTime to,
202         dcp::Formulation formulation,
203         boost::filesystem::path directory
204         )
205 {
206         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to, formulation);
207
208         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
209                 boost::filesystem::path path = directory;
210                 path /= tidy_for_filename (i->cinema->name) + ".zip";
211                 i->make_zip_file (film, path);
212         }
213 }
214
215 void
216 email_kdms (
217         shared_ptr<const Film> film,
218         list<shared_ptr<Screen> > screens,
219         boost::filesystem::path cpl,
220         dcp::LocalTime from,
221         dcp::LocalTime to,
222         dcp::Formulation formulation
223         )
224 {
225         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, cpl, from, to, formulation);
226
227         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
228                 
229                 boost::filesystem::path zip_file = boost::filesystem::temp_directory_path ();
230                 zip_file /= boost::filesystem::unique_path().string() + ".zip";
231                 i->make_zip_file (film, zip_file);
232                 
233                 /* Send email */
234                 
235                 quickmail_initialize ();
236
237                 stringstream start;
238                 start << from.date() << " " << from.time_of_day();
239                 stringstream end;
240                 end << to.date() << " " << to.time_of_day();
241                 
242                 string subject = Config::instance()->kdm_subject();
243                 boost::algorithm::replace_all (subject, "$CPL_NAME", film->dcp_name ());
244                 boost::algorithm::replace_all (subject, "$START_TIME", start.str ());
245                 boost::algorithm::replace_all (subject, "$END_TIME", end.str ());
246                 boost::algorithm::replace_all (subject, "$CINEMA_NAME", i->cinema->name);
247                 quickmail mail = quickmail_create (Config::instance()->kdm_from().c_str(), subject.c_str ());
248                 
249                 quickmail_add_to (mail, i->cinema->email.c_str ());
250                 if (!Config::instance()->kdm_cc().empty ()) {
251                         quickmail_add_cc (mail, Config::instance()->kdm_cc().c_str ());
252                 }
253                 if (!Config::instance()->kdm_bcc().empty ()) {
254                         quickmail_add_bcc (mail, Config::instance()->kdm_bcc().c_str ());
255                 }
256                 
257                 string body = Config::instance()->kdm_email().c_str();
258                 boost::algorithm::replace_all (body, "$CPL_NAME", film->dcp_name ());
259                 boost::algorithm::replace_all (body, "$START_TIME", start.str ());
260                 boost::algorithm::replace_all (body, "$END_TIME", end.str ());
261                 boost::algorithm::replace_all (body, "$CINEMA_NAME", i->cinema->name);
262                 
263                 stringstream screens;
264                 for (list<ScreenKDM>::const_iterator j = i->screen_kdms.begin(); j != i->screen_kdms.end(); ++j) {
265                         screens << j->screen->name << ", ";
266                 }
267                 boost::algorithm::replace_all (body, "$SCREENS", screens.str().substr (0, screens.str().length() - 2));
268
269                 quickmail_set_body (mail, body.c_str());
270                 quickmail_add_attachment_file (mail, zip_file.string().c_str(), "application/zip");
271
272                 int const port = Config::instance()->mail_user().empty() ? 25 : 587;
273
274                 char const* error = quickmail_send (
275                         mail,
276                         Config::instance()->mail_server().c_str(),
277                         port,
278                         Config::instance()->mail_user().c_str(),
279                         Config::instance()->mail_password().c_str()
280                         );
281                 
282                 if (error) {
283                         quickmail_destroy (mail);
284                         throw KDMError (String::compose ("Failed to send KDM email (%1)", error));
285                 }
286                 quickmail_destroy (mail);
287         }
288 }