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 "kdm.h"
26 #include "cinema.h"
27 #include "exceptions.h"
28 #include "util.h"
29 #include "film.h"
30 #include "config.h"
31
32 using std::list;
33 using std::string;
34 using std::stringstream;
35 using boost::shared_ptr;
36
37 struct ScreenKDM
38 {
39         ScreenKDM (shared_ptr<Screen> s, dcp::EncryptedKDM k)
40                 : screen (s)
41                 , kdm (k)
42         {}
43         
44         shared_ptr<Screen> screen;
45         dcp::EncryptedKDM kdm;
46 };
47
48 static string
49 kdm_filename (shared_ptr<const Film> film, ScreenKDM kdm)
50 {
51         return tidy_for_filename (film->name()) + "_" + tidy_for_filename (kdm.screen->cinema->name) + "_" + tidy_for_filename (kdm.screen->name) + ".kdm.xml";
52 }
53
54 struct CinemaKDMs
55 {
56         shared_ptr<Cinema> cinema;
57         list<ScreenKDM> screen_kdms;
58
59         void make_zip_file (shared_ptr<const Film> film, boost::filesystem::path zip_file) const
60         {
61                 int error;
62                 struct zip* zip = zip_open (zip_file.string().c_str(), ZIP_CREATE | ZIP_EXCL, &error);
63                 if (!zip) {
64                         if (error == ZIP_ER_EXISTS) {
65                                 throw FileError ("ZIP file already exists", zip_file);
66                         }
67                         throw FileError ("could not create ZIP file", zip_file);
68                 }
69                 
70                 list<shared_ptr<string> > kdm_strings;
71                 
72                 for (list<ScreenKDM>::const_iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
73                         shared_ptr<string> kdm (new string (i->kdm.as_xml ()));
74                         kdm_strings.push_back (kdm);
75                         
76                         struct zip_source* source = zip_source_buffer (zip, kdm->c_str(), kdm->length(), 0);
77                         if (!source) {
78                                 throw StringError ("could not create ZIP source");
79                         }
80                         
81                         if (zip_add (zip, kdm_filename (film, *i).c_str(), source) == -1) {
82                                 throw StringError ("failed to add KDM to ZIP archive");
83                         }
84                 }
85                 
86                 if (zip_close (zip) == -1) {
87                         throw StringError ("failed to close ZIP archive");
88                 }
89         }
90 };
91
92 /* Not complete but sufficient for our purposes (we're using
93    ScreenKDM in a list where all the screens will be unique).
94 */
95 bool
96 operator== (ScreenKDM const & a, ScreenKDM const & b)
97 {
98         return a.screen == b.screen;
99 }
100
101 static list<ScreenKDM>
102 make_screen_kdms (
103         shared_ptr<const Film> film,
104         list<shared_ptr<Screen> > screens,
105         boost::filesystem::path dcp,
106         dcp::LocalTime from,
107         dcp::LocalTime to
108         )
109 {
110         list<dcp::EncryptedKDM> kdms = film->make_kdms (screens, dcp, from, to);
111            
112         list<ScreenKDM> screen_kdms;
113         
114         list<shared_ptr<Screen> >::iterator i = screens.begin ();
115         list<dcp::EncryptedKDM>::iterator j = kdms.begin ();
116         while (i != screens.end() && j != kdms.end ()) {
117                 screen_kdms.push_back (ScreenKDM (*i, *j));
118                 ++i;
119                 ++j;
120         }
121
122         return screen_kdms;
123 }
124
125 static list<CinemaKDMs>
126 make_cinema_kdms (
127         shared_ptr<const Film> film,
128         list<shared_ptr<Screen> > screens,
129         boost::filesystem::path dcp,
130         dcp::LocalTime from,
131         dcp::LocalTime to
132         )
133 {
134         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
135         list<CinemaKDMs> cinema_kdms;
136
137         while (!screen_kdms.empty ()) {
138                 
139                 /* Get all the screens from a single cinema */
140
141                 CinemaKDMs ck;
142                 
143                 list<ScreenKDM>::iterator i = screen_kdms.begin ();
144                 ck.cinema = i->screen->cinema;
145                 ck.screen_kdms.push_back (*i);
146                 list<ScreenKDM>::iterator j = i;
147                 ++i;
148                 screen_kdms.remove (*j);
149                 
150                 while (i != screen_kdms.end ()) {
151                         if (i->screen->cinema == ck.cinema) {
152                                 ck.screen_kdms.push_back (*i);
153                                 list<ScreenKDM>::iterator j = i;
154                                 ++i;
155                                 screen_kdms.remove (*j);
156                         } else {
157                                 ++i;
158                         }
159                 }
160
161                 cinema_kdms.push_back (ck);
162         }
163
164         return cinema_kdms;
165 }
166
167 void
168 write_kdm_files (
169         shared_ptr<const Film> film,
170         list<shared_ptr<Screen> > screens,
171         boost::filesystem::path dcp,
172         dcp::LocalTime from,
173         dcp::LocalTime to,
174         boost::filesystem::path directory
175         )
176 {
177         list<ScreenKDM> screen_kdms = make_screen_kdms (film, screens, dcp, from, to);
178
179         /* Write KDMs to the specified directory */
180         for (list<ScreenKDM>::iterator i = screen_kdms.begin(); i != screen_kdms.end(); ++i) {
181                 boost::filesystem::path out = directory;
182                 out /= kdm_filename (film, *i);
183                 i->kdm.as_xml (out);
184         }
185 }
186
187 void
188 write_kdm_zip_files (
189         shared_ptr<const Film> film,
190         list<shared_ptr<Screen> > screens,
191         boost::filesystem::path dcp,
192         dcp::LocalTime from,
193         dcp::LocalTime to,
194         boost::filesystem::path directory
195         )
196 {
197         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
198
199         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
200                 boost::filesystem::path path = directory;
201                 path /= tidy_for_filename (i->cinema->name) + ".zip";
202                 i->make_zip_file (film, path);
203         }
204 }
205
206 void
207 email_kdms (
208         shared_ptr<const Film> film,
209         list<shared_ptr<Screen> > screens,
210         boost::filesystem::path dcp,
211         dcp::LocalTime from,
212         dcp::LocalTime to
213         )
214 {
215         list<CinemaKDMs> cinema_kdms = make_cinema_kdms (film, screens, dcp, from, to);
216
217         for (list<CinemaKDMs>::const_iterator i = cinema_kdms.begin(); i != cinema_kdms.end(); ++i) {
218                 
219                 boost::filesystem::path zip_file = boost::filesystem::temp_directory_path ();
220                 zip_file /= boost::filesystem::unique_path().string() + ".zip";
221                 i->make_zip_file (film, zip_file);
222                 
223                 /* Send email */
224                 
225                 quickmail_initialize ();
226                 quickmail mail = quickmail_create (Config::instance()->kdm_from().c_str(), "KDM delivery");
227                 quickmail_add_to (mail, i->cinema->email.c_str ());
228                 
229                 string body = Config::instance()->kdm_email().c_str();
230                 boost::algorithm::replace_all (body, "$CPL_NAME", film->dcp_name ());
231                 stringstream start;
232                 start << from.date() << " " << from.time_of_day();
233                 boost::algorithm::replace_all (body, "$START_TIME", start.str ());
234                 stringstream end;
235                 end << to.date() << " " << to.time_of_day();
236                 boost::algorithm::replace_all (body, "$END_TIME", end.str ());
237
238                 quickmail_set_body (mail, body.c_str());
239                 quickmail_add_attachment_file (mail, zip_file.string().c_str(), "application/zip");
240
241                 int const port = Config::instance()->mail_user().empty() ? 25 : 587;
242
243                 char const* error = quickmail_send (
244                         mail,
245                         Config::instance()->mail_server().c_str(),
246                         port,
247                         Config::instance()->mail_user().c_str(),
248                         Config::instance()->mail_password().c_str()
249                         );
250                 
251                 if (error) {
252                         quickmail_destroy (mail);
253                         throw KDMError (String::compose ("Failed to send KDM email (%1)", error));
254                 }
255                 quickmail_destroy (mail);
256         }
257 }