Merge branch 'resample-drop-frame'
[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                         throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (s)));
82                 }
83         }
84
85         ~SSHSCP ()
86         {
87                 ssh_scp_free (scp);
88         }
89
90         ssh_scp scp;
91 };
92
93
94 SCPDCPJob::SCPDCPJob (shared_ptr<const FilmState> s, Log* l)
95         : Job (s, shared_ptr<const Options> (), l)
96         , _status ("Waiting")
97 {
98
99 }
100
101 string
102 SCPDCPJob::name () const
103 {
104         return "Copy DCP to TMS";
105 }
106
107 void
108 SCPDCPJob::run ()
109 {
110         _log->log ("SCP DCP job starting");
111         
112         SSHSession ss;
113         
114         set_status ("connecting");
115         
116         ssh_options_set (ss.session, SSH_OPTIONS_HOST, Config::instance()->tms_ip().c_str ());
117         ssh_options_set (ss.session, SSH_OPTIONS_USER, Config::instance()->tms_user().c_str ());
118         int const port = 22;
119         ssh_options_set (ss.session, SSH_OPTIONS_PORT, &port);
120         
121         int r = ss.connect ();
122         if (r != SSH_OK) {
123                 throw NetworkError (String::compose ("Could not connect to server %1 (%2)", Config::instance()->tms_ip(), ssh_get_error (ss.session)));
124         }
125         
126         int const state = ssh_is_server_known (ss.session);
127         if (state == SSH_SERVER_ERROR) {
128                 throw NetworkError (String::compose ("SSH error (%1)", ssh_get_error (ss.session)));
129         }
130         
131         r = ssh_userauth_password (ss.session, 0, Config::instance()->tms_password().c_str ());
132         if (r != SSH_AUTH_SUCCESS) {
133                 throw NetworkError (String::compose ("Failed to authenticate with server (%1)", ssh_get_error (ss.session)));
134         }
135         
136         SSHSCP sc (ss.session);
137         
138         r = ssh_scp_init (sc.scp);
139         if (r != SSH_OK) {
140                 throw NetworkError (String::compose ("Could not start SCP session (%1)", ssh_get_error (ss.session)));
141         }
142         
143         r = ssh_scp_push_directory (sc.scp, _fs->name.c_str(), S_IRWXU);
144         if (r != SSH_OK) {
145                 throw NetworkError (String::compose ("Could not create remote directory %1 (%2)", _fs->name, ssh_get_error (ss.session)));
146         }
147         
148         string const dcp_dir = _fs->dir (_fs->name);
149         
150         boost::uintmax_t bytes_to_transfer = 0;
151         for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
152                 bytes_to_transfer += filesystem::file_size (*i);
153         }
154         
155         boost::uintmax_t buffer_size = 64 * 1024;
156         char buffer[buffer_size];
157         boost::uintmax_t bytes_transferred = 0;
158         
159         for (filesystem::directory_iterator i = filesystem::directory_iterator (dcp_dir); i != filesystem::directory_iterator(); ++i) {
160                 
161                 /* Aah, the sweet smell of progress */
162 #if BOOST_FILESYSTEM_VERSION == 3               
163                 string const leaf = filesystem::path(*i).leaf().generic_string ();
164 #else
165                 string const leaf = i->leaf ();
166 #endif
167                 
168                 set_status ("copying " + leaf);
169                 
170                 boost::uintmax_t to_do = filesystem::file_size (*i);
171                 ssh_scp_push_file (sc.scp, leaf.c_str(), to_do, S_IRUSR | S_IWUSR);
172
173                 FILE* f = fopen (filesystem::path (*i).string().c_str(), "rb");
174                 if (f == 0) {
175                         throw NetworkError (String::compose ("Could not open %1 to send", *i));
176                 }
177
178                 while (to_do > 0) {
179                         int const t = min (to_do, buffer_size);
180                         size_t const read = fread (buffer, 1, t, f);
181                         if (read != size_t (t)) {
182                                 throw ReadFileError (filesystem::path (*i).string());
183                         }
184                         
185                         r = ssh_scp_write (sc.scp, buffer, t);
186                         if (r != SSH_OK) {
187                                 throw NetworkError (String::compose ("Could not write to remote file (%1)", ssh_get_error (ss.session)));
188                         }
189                         to_do -= t;
190                         bytes_transferred += t;
191                         
192                         set_progress ((double) bytes_transferred / bytes_to_transfer);
193                 }
194
195                 fclose (f);
196         }
197         
198         set_progress (1);
199         set_status ("");
200         set_state (FINISHED_OK);
201 }
202
203 string
204 SCPDCPJob::status () const
205 {
206         boost::mutex::scoped_lock lm (_status_mutex);
207         stringstream s;
208         s << Job::status ();
209         if (!_status.empty ()) {
210                 s << "; " << _status;
211         }
212         return s.str ();
213 }
214
215 void
216 SCPDCPJob::set_status (string s)
217 {
218         boost::mutex::scoped_lock lm (_status_mutex);
219         _status = s;
220 }
221