Merge FilmState / Film.
[dcpomatic.git] / src / lib / scp_dcp_job.cc
1 /*
2     Copyright (C) 2012 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 /** @file src/scp_dcp_job.cc
21  *  @brief A job to copy DCPs to a SCP-enabled server.
22  */
23
24 #include <iostream>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <fcntl.h>
28 #include <boost/filesystem.hpp>
29 #include <libssh/libssh.h>
30 #include "compose.hpp"
31 #include "scp_dcp_job.h"
32 #include "exceptions.h"
33 #include "config.h"
34 #include "log.h"
35 #include "film.h"
36
37 using namespace std;
38 using namespace boost;
39
40 class SSHSession
41 {
42 public:
43         SSHSession ()
44                 : _connected (false)
45         {
46                 session = ssh_new ();
47                 if (session == 0) {
48                         throw NetworkError ("Could not start SSH session");
49                 }
50         }
51
52         int connect ()
53         {
54                 int r = ssh_connect (session);
55                 if (r == 0) {
56                         _connected = true;
57                 }
58                 return r;
59         }
60
61         ~SSHSession ()
62         {
63                 if (_connected) {
64                         ssh_disconnect (session);
65                 }
66                 ssh_free (session);
67         }
68
69         ssh_session session;
70
71 private:        
72         bool _connected;
73 };
74
75 class SSHSCP
76 {
77 public:
78         SSHSCP (ssh_session s)
79         {
80                 scp = ssh_scp_new (s, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, Config::instance()->tms_path().c_str ());
81                 if (!scp) {
82                         throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (s)));
83                 }
84         }
85
86         ~SSHSCP ()
87         {
88                 ssh_scp_free (scp);
89         }
90
91         ssh_scp scp;
92 };
93
94
95 SCPDCPJob::SCPDCPJob (shared_ptr<Film> f, shared_ptr<Job> req)
96         : Job (f, req)
97         , _status ("Waiting")
98 {
99
100 }
101
102 string
103 SCPDCPJob::name () const
104 {
105         return "Copy DCP to TMS";
106 }
107
108 void
109 SCPDCPJob::run ()
110 {
111         _film->log()->log ("SCP DCP job starting");
112         
113         SSHSession ss;
114         
115         set_status ("connecting");
116         
117         ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ());
118         ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ());
119         int const port = 22;
120         ssh_options_set (ss.session, SSH_OPTIONS_PORT, &port);
121         
122         int r = ss.connect ();
123         if (r != SSH_OK) {
124                 throw NetworkError (String::compose ("Could not connect to server %1 (%2)", Config::instance()->tms_ip(), ssh_get_error (ss.session)));
125         }
126         
127         int const state = ssh_is_server_known (ss.session);
128         if (state == SSH_SERVER_ERROR) {
129                 throw NetworkError (String::compose ("SSH error (%1)", ssh_get_error (ss.session)));
130         }
131         
132         r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ());
133         if (r != SSH_AUTH_SUCCESS) {
134                 throw NetworkError (String::compose ("Failed to authenticate with server (%1)", ssh_get_error (ss.session)));
135         }
136         
137         SSHSCP sc (ss.session);
138         
139         r = ssh_scp_init (sc.scp);
140         if (r != SSH_OK) {
141                 throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (ss.session)));
142         }
143         
144         r = ssh_scp_push_directory (sc.scp, _film->dcp_name().c_str(), S_IRWXU);
145         if (r != SSH_OK) {
146                 throw NetworkError (String::compose ("Could not create remote directory %1 (%2)", _film->dcp_name(), ssh_get_error (ss.session)));
147         }
148         
149         string const dcp_dir = _film->dir (_film->dcp_name());
150         
151         boost::uintmax_t bytes_to_transfer = 0;
152         for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
153                 bytes_to_transfer += filesystem::file_size (*i);
154         }
155         
156         boost::uintmax_t buffer_size = 64 * 1024;
157         char buffer[buffer_size];
158         boost::uintmax_t bytes_transferred = 0;
159         
160         for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
161                 
162                 /* Aah, the sweet smell of progress */
163 #if BOOST_FILESYSTEM_VERSION == 3               
164                 string const leaf = filesystem::path(*i).leaf().generic_string ();
165 #else
166                 string const leaf = i->leaf ();
167 #endif
168                 
169                 set_status ("copying " + leaf);
170                 
171                 boost::uintmax_t to_do = filesystem::file_size (*i);
172                 ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR);
173
174                 FILE* f = fopen (filesystem::path (*i).string().c_str(), "rb");
175                 if (f == 0) {
176                         throw NetworkError (String::compose ("Could not open %1 to send", *i));
177                 }
178
179                 while (to_do > 0) {
180                         int const t = min (to_do, buffer_size);
181                         size_t const read = fread (buffer, 1, t, f);
182                         if (read != size_t (t)) {
183                                 throw ReadFileError (filesystem::path (*i).string());
184                         }
185                         
186                         r = ssh_scp_write (sc.scp, buffer, t);
187                         if (r != SSH_OK) {
188                                 throw NetworkError (String::compose ("Could not write to remote file (%1)", ssh_get_error (ss.session)));
189                         }
190                         to_do -= t;
191                         bytes_transferred += t;
192                         
193                         set_progress ((double) bytes_transferred / bytes_to_transfer);
194                 }
195
196                 fclose (f);
197         }
198         
199         set_progress (1);
200         set_status ("");
201         set_state (FINISHED_OK);
202 }
203
204 string
205 SCPDCPJob::status () const
206 {
207         boost::mutex::scoped_lock lm (_status_mutex);
208         stringstream s;
209         s << Job::status ();
210         if (!_status.empty ()) {
211                 s << "; " << _status;
212         }
213         return s.str ();
214 }
215
216 void
217 SCPDCPJob::set_status (string s)
218 {
219         boost::mutex::scoped_lock lm (_status_mutex);
220         _status = s;
221 }
222