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