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