Part of work to add emailing of KDMs.
[dcpomatic.git] / src / lib / util.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3     Copyright (C) 2000-2007 Paul Davis
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 /** @file src/lib/util.cc
22  *  @brief Some utility functions and classes.
23  */
24
25 #include <sstream>
26 #include <iomanip>
27 #include <iostream>
28 #include <fstream>
29 #include <climits>
30 #ifdef DCPOMATIC_POSIX
31 #include <execinfo.h>
32 #include <cxxabi.h>
33 #endif
34 #include <libssh/libssh.h>
35 #include <signal.h>
36 #include <boost/algorithm/string.hpp>
37 #include <boost/bind.hpp>
38 #include <boost/lambda/lambda.hpp>
39 #include <boost/lexical_cast.hpp>
40 #include <boost/thread.hpp>
41 #include <boost/filesystem.hpp>
42 #include <glib.h>
43 #include <openjpeg.h>
44 #include <openssl/md5.h>
45 #include <magick/MagickCore.h>
46 #include <magick/version.h>
47 #include <libdcp/version.h>
48 #include <libdcp/util.h>
49 extern "C" {
50 #include <libavcodec/avcodec.h>
51 #include <libavformat/avformat.h>
52 #include <libswscale/swscale.h>
53 #include <libavfilter/avfiltergraph.h>
54 #include <libpostproc/postprocess.h>
55 #include <libavutil/pixfmt.h>
56 }
57 #include <curl/curl.h>
58 #include "util.h"
59 #include "exceptions.h"
60 #include "scaler.h"
61 #include "dcp_content_type.h"
62 #include "filter.h"
63 #include "sound_processor.h"
64 #include "config.h"
65 #include "ratio.h"
66 #include "job.h"
67 #ifdef DCPOMATIC_WINDOWS
68 #include "stack.hpp"
69 #endif
70
71 #include "i18n.h"
72
73 using std::string;
74 using std::stringstream;
75 using std::setfill;
76 using std::ostream;
77 using std::endl;
78 using std::vector;
79 using std::hex;
80 using std::setw;
81 using std::ifstream;
82 using std::ios;
83 using std::min;
84 using std::max;
85 using std::list;
86 using std::multimap;
87 using std::istream;
88 using std::numeric_limits;
89 using std::pair;
90 using std::ofstream;
91 using boost::shared_ptr;
92 using boost::thread;
93 using boost::lexical_cast;
94 using boost::optional;
95 using libdcp::Size;
96
97 static boost::thread::id ui_thread;
98 static boost::filesystem::path backtrace_file;
99
100 /** Convert some number of seconds to a string representation
101  *  in hours, minutes and seconds.
102  *
103  *  @param s Seconds.
104  *  @return String of the form H:M:S (where H is hours, M
105  *  is minutes and S is seconds).
106  */
107 string
108 seconds_to_hms (int s)
109 {
110         int m = s / 60;
111         s -= (m * 60);
112         int h = m / 60;
113         m -= (h * 60);
114
115         stringstream hms;
116         hms << h << N_(":");
117         hms.width (2);
118         hms << std::setfill ('0') << m << N_(":");
119         hms.width (2);
120         hms << std::setfill ('0') << s;
121
122         return hms.str ();
123 }
124
125 /** @param s Number of seconds.
126  *  @return String containing an approximate description of s (e.g. "about 2 hours")
127  */
128 string
129 seconds_to_approximate_hms (int s)
130 {
131         int m = s / 60;
132         s -= (m * 60);
133         int h = m / 60;
134         m -= (h * 60);
135
136         stringstream ap;
137         
138         if (h > 0) {
139                 if (m > 30) {
140                         ap << (h + 1) << N_(" ") << _("hours");
141                 } else {
142                         if (h == 1) {
143                                 ap << N_("1 ") << _("hour");
144                         } else {
145                                 ap << h << N_(" ") << _("hours");
146                         }
147                 }
148         } else if (m > 0) {
149                 if (m == 1) {
150                         ap << N_("1 ") << _("minute");
151                 } else {
152                         ap << m << N_(" ") << _("minutes");
153                 }
154         } else {
155                 ap << s << N_(" ") << _("seconds");
156         }
157
158         return ap.str ();
159 }
160
161 #ifdef DCPOMATIC_POSIX
162 /** @param l Mangled C++ identifier.
163  *  @return Demangled version.
164  */
165 static string
166 demangle (string l)
167 {
168         string::size_type const b = l.find_first_of (N_("("));
169         if (b == string::npos) {
170                 return l;
171         }
172
173         string::size_type const p = l.find_last_of (N_("+"));
174         if (p == string::npos) {
175                 return l;
176         }
177
178         if ((p - b) <= 1) {
179                 return l;
180         }
181         
182         string const fn = l.substr (b + 1, p - b - 1);
183
184         int status;
185         try {
186                 
187                 char* realname = abi::__cxa_demangle (fn.c_str(), 0, 0, &status);
188                 string d (realname);
189                 free (realname);
190                 return d;
191                 
192         } catch (std::exception) {
193                 
194         }
195         
196         return l;
197 }
198
199 /** Write a stacktrace to an ostream.
200  *  @param out Stream to write to.
201  *  @param levels Number of levels to go up the call stack.
202  */
203 void
204 stacktrace (ostream& out, int levels)
205 {
206         void *array[200];
207         size_t size = backtrace (array, 200);
208         char** strings = backtrace_symbols (array, size);
209      
210         if (strings) {
211                 for (size_t i = 0; i < size && (levels == 0 || i < size_t(levels)); i++) {
212                         out << N_("  ") << demangle (strings[i]) << "\n";
213                 }
214                 
215                 free (strings);
216         }
217 }
218 #endif
219
220 /** @param v Version as used by FFmpeg.
221  *  @return A string representation of v.
222  */
223 static string
224 ffmpeg_version_to_string (int v)
225 {
226         stringstream s;
227         s << ((v & 0xff0000) >> 16) << N_(".") << ((v & 0xff00) >> 8) << N_(".") << (v & 0xff);
228         return s.str ();
229 }
230
231 /** Return a user-readable string summarising the versions of our dependencies */
232 string
233 dependency_version_summary ()
234 {
235         stringstream s;
236         s << N_("libopenjpeg ") << opj_version () << N_(", ")
237           << N_("libavcodec ") << ffmpeg_version_to_string (avcodec_version()) << N_(", ")
238           << N_("libavfilter ") << ffmpeg_version_to_string (avfilter_version()) << N_(", ")
239           << N_("libavformat ") << ffmpeg_version_to_string (avformat_version()) << N_(", ")
240           << N_("libavutil ") << ffmpeg_version_to_string (avutil_version()) << N_(", ")
241           << N_("libpostproc ") << ffmpeg_version_to_string (postproc_version()) << N_(", ")
242           << N_("libswscale ") << ffmpeg_version_to_string (swscale_version()) << N_(", ")
243           << MagickVersion << N_(", ")
244           << N_("libssh ") << ssh_version (0) << N_(", ")
245           << N_("libdcp ") << libdcp::version << N_(" git ") << libdcp::git_commit;
246
247         return s.str ();
248 }
249
250 double
251 seconds (struct timeval t)
252 {
253         return t.tv_sec + (double (t.tv_usec) / 1e6);
254 }
255
256 #ifdef DCPOMATIC_WINDOWS
257 LONG WINAPI exception_handler(struct _EXCEPTION_POINTERS *)
258 {
259         dbg::stack s;
260         ofstream f (backtrace_file.string().c_str());
261         std::copy(s.begin(), s.end(), std::ostream_iterator<dbg::stack_frame>(f, "\n"));
262         return EXCEPTION_CONTINUE_SEARCH;
263 }
264 #endif
265
266 /** Call the required functions to set up DCP-o-matic's static arrays, etc.
267  *  Must be called from the UI thread, if there is one.
268  */
269 void
270 dcpomatic_setup ()
271 {
272 #ifdef DCPOMATIC_WINDOWS
273         backtrace_file /= g_get_user_config_dir ();
274         backtrace_file /= "backtrace.txt";
275         SetUnhandledExceptionFilter(exception_handler);
276 #endif  
277         
278         avfilter_register_all ();
279
280         libdcp::init ();
281         
282         Ratio::setup_ratios ();
283         DCPContentType::setup_dcp_content_types ();
284         Scaler::setup_scalers ();
285         Filter::setup_filters ();
286         SoundProcessor::setup_sound_processors ();
287
288         ui_thread = boost::this_thread::get_id ();
289 }
290
291 #ifdef DCPOMATIC_WINDOWS
292 boost::filesystem::path
293 mo_path ()
294 {
295         wchar_t buffer[512];
296         GetModuleFileName (0, buffer, 512 * sizeof(wchar_t));
297         boost::filesystem::path p (buffer);
298         p = p.parent_path ();
299         p = p.parent_path ();
300         p /= "locale";
301         return p;
302 }
303 #endif
304
305 void
306 dcpomatic_setup_gettext_i18n (string lang)
307 {
308 #ifdef DCPOMATIC_POSIX
309         lang += ".UTF8";
310 #endif
311
312         if (!lang.empty ()) {
313                 /* Override our environment language; this is essential on
314                    Windows.
315                 */
316                 char cmd[64];
317                 snprintf (cmd, sizeof(cmd), "LANGUAGE=%s", lang.c_str ());
318                 putenv (cmd);
319                 snprintf (cmd, sizeof(cmd), "LANG=%s", lang.c_str ());
320                 putenv (cmd);
321         }
322
323         setlocale (LC_ALL, "");
324         textdomain ("libdcpomatic");
325
326 #ifdef DCPOMATIC_WINDOWS
327         bindtextdomain ("libdcpomatic", mo_path().string().c_str());
328         bind_textdomain_codeset ("libdcpomatic", "UTF8");
329 #endif  
330
331 #ifdef DCPOMATIC_POSIX
332         bindtextdomain ("libdcpomatic", POSIX_LOCALE_PREFIX);
333 #endif
334 }
335
336 /** @param s A string.
337  *  @return Parts of the string split at spaces, except when a space is within quotation marks.
338  */
339 vector<string>
340 split_at_spaces_considering_quotes (string s)
341 {
342         vector<string> out;
343         bool in_quotes = false;
344         string c;
345         for (string::size_type i = 0; i < s.length(); ++i) {
346                 if (s[i] == ' ' && !in_quotes) {
347                         out.push_back (c);
348                         c = N_("");
349                 } else if (s[i] == '"') {
350                         in_quotes = !in_quotes;
351                 } else {
352                         c += s[i];
353                 }
354         }
355
356         out.push_back (c);
357         return out;
358 }
359
360 string
361 md5_digest (void const * data, int size)
362 {
363         MD5_CTX md5_context;
364         MD5_Init (&md5_context);
365         MD5_Update (&md5_context, data, size);
366         unsigned char digest[MD5_DIGEST_LENGTH];
367         MD5_Final (digest, &md5_context);
368         
369         stringstream s;
370         for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
371                 s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
372         }
373
374         return s.str ();
375 }
376
377 /** @param file File name.
378  *  @return MD5 digest of file's contents.
379  */
380 string
381 md5_digest (boost::filesystem::path file)
382 {
383         ifstream f (file.string().c_str(), std::ios::binary);
384         if (!f.good ()) {
385                 throw OpenFileError (file.string());
386         }
387         
388         f.seekg (0, std::ios::end);
389         int bytes = f.tellg ();
390         f.seekg (0, std::ios::beg);
391
392         int const buffer_size = 64 * 1024;
393         char buffer[buffer_size];
394
395         MD5_CTX md5_context;
396         MD5_Init (&md5_context);
397         while (bytes > 0) {
398                 int const t = min (bytes, buffer_size);
399                 f.read (buffer, t);
400                 MD5_Update (&md5_context, buffer, t);
401                 bytes -= t;
402         }
403
404         unsigned char digest[MD5_DIGEST_LENGTH];
405         MD5_Final (digest, &md5_context);
406
407         stringstream s;
408         for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
409                 s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
410         }
411
412         return s.str ();
413 }
414
415 /** @param job Optional job for which to report progress */
416 string
417 md5_digest_directory (boost::filesystem::path directory, shared_ptr<Job> job)
418 {
419         int const buffer_size = 64 * 1024;
420         char buffer[buffer_size];
421
422         MD5_CTX md5_context;
423         MD5_Init (&md5_context);
424
425         int files = 0;
426         if (job) {
427                 for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
428                         ++files;
429                 }
430         }
431
432         int j = 0;
433         for (boost::filesystem::directory_iterator i(directory); i != boost::filesystem::directory_iterator(); ++i) {
434                 ifstream f (i->path().string().c_str(), std::ios::binary);
435                 if (!f.good ()) {
436                         throw OpenFileError (i->path().string());
437                 }
438         
439                 f.seekg (0, std::ios::end);
440                 int bytes = f.tellg ();
441                 f.seekg (0, std::ios::beg);
442
443                 while (bytes > 0) {
444                         int const t = min (bytes, buffer_size);
445                         f.read (buffer, t);
446                         MD5_Update (&md5_context, buffer, t);
447                         bytes -= t;
448                 }
449
450                 if (job) {
451                         job->set_progress (float (j) / files);
452                         ++j;
453                 }
454         }
455
456         unsigned char digest[MD5_DIGEST_LENGTH];
457         MD5_Final (digest, &md5_context);
458
459         stringstream s;
460         for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
461                 s << std::hex << std::setfill('0') << std::setw(2) << ((int) digest[i]);
462         }
463
464         return s.str ();
465 }
466
467 static bool
468 about_equal (float a, float b)
469 {
470         /* A film of F seconds at f FPS will be Ff frames;
471            Consider some delta FPS d, so if we run the same
472            film at (f + d) FPS it will last F(f + d) seconds.
473
474            Hence the difference in length over the length of the film will
475            be F(f + d) - Ff frames
476             = Ff + Fd - Ff frames
477             = Fd frames
478             = Fd/f seconds
479  
480            So if we accept a difference of 1 frame, ie 1/f seconds, we can
481            say that
482
483            1/f = Fd/f
484         ie 1 = Fd
485         ie d = 1/F
486  
487            So for a 3hr film, ie F = 3 * 60 * 60 = 10800, the acceptable
488            FPS error is 1/F ~= 0.0001 ~= 10-e4
489         */
490
491         return (fabs (a - b) < 1e-4);
492 }
493
494 /** @param An arbitrary audio frame rate.
495  *  @return The appropriate DCP-approved frame rate (48kHz or 96kHz).
496  */
497 int
498 dcp_audio_frame_rate (int fs)
499 {
500         if (fs <= 48000) {
501                 return 48000;
502         }
503
504         return 96000;
505 }
506
507 Socket::Socket (int timeout)
508         : _deadline (_io_service)
509         , _socket (_io_service)
510         , _timeout (timeout)
511 {
512         _deadline.expires_at (boost::posix_time::pos_infin);
513         check ();
514 }
515
516 void
517 Socket::check ()
518 {
519         if (_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now ()) {
520                 _socket.close ();
521                 _deadline.expires_at (boost::posix_time::pos_infin);
522         }
523
524         _deadline.async_wait (boost::bind (&Socket::check, this));
525 }
526
527 /** Blocking connect.
528  *  @param endpoint End-point to connect to.
529  */
530 void
531 Socket::connect (boost::asio::ip::basic_resolver_entry<boost::asio::ip::tcp> const & endpoint)
532 {
533         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
534         boost::system::error_code ec = boost::asio::error::would_block;
535         _socket.async_connect (endpoint, boost::lambda::var(ec) = boost::lambda::_1);
536         do {
537                 _io_service.run_one();
538         } while (ec == boost::asio::error::would_block);
539
540         if (ec || !_socket.is_open ()) {
541                 throw NetworkError (_("connect timed out"));
542         }
543 }
544
545 /** Blocking write.
546  *  @param data Buffer to write.
547  *  @param size Number of bytes to write.
548  */
549 void
550 Socket::write (uint8_t const * data, int size)
551 {
552         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
553         boost::system::error_code ec = boost::asio::error::would_block;
554
555         boost::asio::async_write (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
556         
557         do {
558                 _io_service.run_one ();
559         } while (ec == boost::asio::error::would_block);
560
561         if (ec) {
562                 throw NetworkError (ec.message ());
563         }
564 }
565
566 void
567 Socket::write (uint32_t v)
568 {
569         v = htonl (v);
570         write (reinterpret_cast<uint8_t*> (&v), 4);
571 }
572
573 /** Blocking read.
574  *  @param data Buffer to read to.
575  *  @param size Number of bytes to read.
576  */
577 void
578 Socket::read (uint8_t* data, int size)
579 {
580         _deadline.expires_from_now (boost::posix_time::seconds (_timeout));
581         boost::system::error_code ec = boost::asio::error::would_block;
582
583         boost::asio::async_read (_socket, boost::asio::buffer (data, size), boost::lambda::var(ec) = boost::lambda::_1);
584
585         do {
586                 _io_service.run_one ();
587         } while (ec == boost::asio::error::would_block);
588         
589         if (ec) {
590                 throw NetworkError (ec.message ());
591         }
592 }
593
594 uint32_t
595 Socket::read_uint32 ()
596 {
597         uint32_t v;
598         read (reinterpret_cast<uint8_t *> (&v), 4);
599         return ntohl (v);
600 }
601
602 /** Round a number up to the nearest multiple of another number.
603  *  @param c Index.
604  *  @param s Array of numbers to round, indexed by c.
605  *  @param t Multiple to round to.
606  *  @return Rounded number.
607  */
608 int
609 stride_round_up (int c, int const * stride, int t)
610 {
611         int const a = stride[c] + (t - 1);
612         return a - (a % t);
613 }
614
615 /** Read a sequence of key / value pairs from a text stream;
616  *  the keys are the first words on the line, and the values are
617  *  the remainder of the line following the key.  Lines beginning
618  *  with # are ignored.
619  *  @param s Stream to read.
620  *  @return key/value pairs.
621  */
622 multimap<string, string>
623 read_key_value (istream &s) 
624 {
625         multimap<string, string> kv;
626         
627         string line;
628         while (getline (s, line)) {
629                 if (line.empty ()) {
630                         continue;
631                 }
632
633                 if (line[0] == '#') {
634                         continue;
635                 }
636
637                 if (line[line.size() - 1] == '\r') {
638                         line = line.substr (0, line.size() - 1);
639                 }
640
641                 size_t const s = line.find (' ');
642                 if (s == string::npos) {
643                         continue;
644                 }
645
646                 kv.insert (make_pair (line.substr (0, s), line.substr (s + 1)));
647         }
648
649         return kv;
650 }
651
652 string
653 get_required_string (multimap<string, string> const & kv, string k)
654 {
655         if (kv.count (k) > 1) {
656                 throw StringError (N_("unexpected multiple keys in key-value set"));
657         }
658
659         multimap<string, string>::const_iterator i = kv.find (k);
660         
661         if (i == kv.end ()) {
662                 throw StringError (String::compose (_("missing key %1 in key-value set"), k));
663         }
664
665         return i->second;
666 }
667
668 int
669 get_required_int (multimap<string, string> const & kv, string k)
670 {
671         string const v = get_required_string (kv, k);
672         return lexical_cast<int> (v);
673 }
674
675 float
676 get_required_float (multimap<string, string> const & kv, string k)
677 {
678         string const v = get_required_string (kv, k);
679         return lexical_cast<float> (v);
680 }
681
682 string
683 get_optional_string (multimap<string, string> const & kv, string k)
684 {
685         if (kv.count (k) > 1) {
686                 throw StringError (N_("unexpected multiple keys in key-value set"));
687         }
688
689         multimap<string, string>::const_iterator i = kv.find (k);
690         if (i == kv.end ()) {
691                 return N_("");
692         }
693
694         return i->second;
695 }
696
697 int
698 get_optional_int (multimap<string, string> const & kv, string k)
699 {
700         if (kv.count (k) > 1) {
701                 throw StringError (N_("unexpected multiple keys in key-value set"));
702         }
703
704         multimap<string, string>::const_iterator i = kv.find (k);
705         if (i == kv.end ()) {
706                 return 0;
707         }
708
709         return lexical_cast<int> (i->second);
710 }
711
712 /** Trip an assert if the caller is not in the UI thread */
713 void
714 ensure_ui_thread ()
715 {
716         assert (boost::this_thread::get_id() == ui_thread);
717 }
718
719 /** @param v Content video frame.
720  *  @param audio_sample_rate Source audio sample rate.
721  *  @param frames_per_second Number of video frames per second.
722  *  @return Equivalent number of audio frames for `v'.
723  */
724 int64_t
725 video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second)
726 {
727         return ((int64_t) v * audio_sample_rate / frames_per_second);
728 }
729
730 string
731 audio_channel_name (int c)
732 {
733         assert (MAX_AUDIO_CHANNELS == 6);
734
735         /* TRANSLATORS: these are the names of audio channels; Lfe (sub) is the low-frequency
736            enhancement channel (sub-woofer)./
737         */
738         string const channels[] = {
739                 _("Left"),
740                 _("Right"),
741                 _("Centre"),
742                 _("Lfe (sub)"),
743                 _("Left surround"),
744                 _("Right surround"),
745         };
746
747         return channels[c];
748 }
749
750 FrameRateConversion::FrameRateConversion (float source, int dcp)
751         : skip (false)
752         , repeat (false)
753         , change_speed (false)
754 {
755         if (fabs (source / 2.0 - dcp) < (fabs (source - dcp))) {
756                 skip = true;
757         } else if (fabs (source * 2 - dcp) < fabs (source - dcp)) {
758                 repeat = true;
759         }
760
761         change_speed = !about_equal (source * factor(), dcp);
762
763         if (!skip && !repeat && !change_speed) {
764                 description = _("Content and DCP have the same rate.\n");
765         } else {
766                 if (skip) {
767                         description = _("DCP will use every other frame of the content.\n");
768                 } else if (repeat) {
769                         description = _("Each content frame will be doubled in the DCP.\n");
770                 }
771
772                 if (change_speed) {
773                         float const pc = dcp * 100 / (source * factor());
774                         description += String::compose (_("DCP will run at %1%% of the content speed.\n"), pc);
775                 }
776         }
777 }
778
779 LocaleGuard::LocaleGuard ()
780         : _old (0)
781 {
782         char const * old = setlocale (LC_NUMERIC, 0);
783
784         if (old) {
785                 _old = strdup (old);
786                 if (strcmp (_old, "C")) {
787                         setlocale (LC_NUMERIC, "C");
788                 }
789         }
790 }
791
792 LocaleGuard::~LocaleGuard ()
793 {
794         setlocale (LC_NUMERIC, _old);
795         free (_old);
796 }
797
798 bool
799 valid_image_file (boost::filesystem::path f)
800 {
801         string ext = f.extension().string();
802         transform (ext.begin(), ext.end(), ext.begin(), ::tolower);
803         return (ext == ".tif" || ext == ".tiff" || ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".bmp" || ext == ".tga");
804 }
805
806 string
807 tidy_for_filename (string f)
808 {
809         string t;
810         for (size_t i = 0; i < f.length(); ++i) {
811                 if (isalnum (f[i]) || f[i] == '_' || f[i] == '-') {
812                         t += f[i];
813                 } else {
814                         t += '_';
815                 }
816         }
817
818         return t;
819 }
820
821 struct EmailState
822 {
823         string message;
824         int done;
825 };
826
827 static size_t
828 send_email_function (void* ptr, size_t size, size_t nmemb, void* userdata)
829 {
830         EmailState* state = reinterpret_cast<EmailState*> (userdata);
831
832         int const now = min (size * nmemb, state->message.length() - state->done);
833
834         memcpy (ptr, state->message.c_str() + state->done, now);
835         state->done += now;
836
837         return now;
838 }
839         
840 bool
841 send_email (string from, string to, string message)
842 {
843         CURL* curl = curl_easy_init ();
844         if (!curl) {
845                 return true;
846         }
847
848         string const url = "smtp://" + Config::instance()->mail_server();
849
850         curl_easy_setopt (curl, CURLOPT_URL, url.c_str ());
851         curl_easy_setopt (curl, CURLOPT_MAIL_FROM, from.c_str ());
852         struct curl_slist* recipients = 0;
853         recipients = curl_slist_append (recipients, to.c_str ());
854         curl_easy_setopt (curl, CURLOPT_READFUNCTION, send_email_function);
855
856         EmailState state;
857         state.message = message;
858         state.done = 0;
859         curl_easy_setopt (curl, CURLOPT_READDATA, &state);
860
861         if (curl_easy_perform (curl) != CURLE_OK) {
862                 return true;
863         }
864
865         curl_slist_free_all (recipients);
866         curl_easy_cleanup (curl);
867
868         return false;
869 }