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