View Javadoc
1   /*
2    * Copyright (C) 2007-2012 Argeo GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.argeo.slc.jsch;
17  
18  import java.io.ByteArrayInputStream;
19  import java.io.ByteArrayOutputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.commons.io.IOUtils;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.argeo.slc.SlcException;
33  import org.springframework.core.io.ByteArrayResource;
34  import org.springframework.core.io.Resource;
35  import org.springframework.util.AntPathMatcher;
36  import org.springframework.util.PathMatcher;
37  import org.springframework.util.StringUtils;
38  
39  import com.jcraft.jsch.Channel;
40  import com.jcraft.jsch.ChannelExec;
41  import com.jcraft.jsch.Session;
42  
43  public class ScpTo extends AbstractJschTask {
44  	private final static Log log = LogFactory.getLog(ScpTo.class);
45  
46  	private Resource localResource;
47  	private String remotePath;
48  
49  	private String dir;
50  	private String remoteDir;
51  	private List<String> includes = new ArrayList<String>();
52  
53  	private List<String> excludes = new ArrayList<String>();
54  
55  	private PathMatcher pathMatcher;
56  
57  	public void run(Session session) {
58  		if (StringUtils.hasText(dir)) {
59  			if (!StringUtils.hasText(remoteDir))
60  				throw new SlcException("Remote dir has to be specified.");
61  
62  			String dirOs = dir.replace('/', File.separatorChar);
63  			if (dirOs.charAt(dir.length() - 1) != File.separatorChar) {
64  				dirOs = dirOs + File.separator;
65  			}
66  
67  			if (pathMatcher == null)
68  				pathMatcher = new AntPathMatcher();
69  
70  			log.info("Start multiple scp based on " + dirOs);
71  			scanDir(session, dirOs, "", includes, excludes);
72  		}
73  
74  		if (localResource != null) {
75  			uploadResource(session, localResource);
76  		}
77  	}
78  
79  	protected void scanDir(Session session, String dir, String currentRelPath,
80  			List<String> includes, List<String> excludes) {
81  		File[] files = new File(dir).listFiles();
82  		for (File file : files) {
83  			if (!file.isDirectory()) {
84  				String relPath = currentRelPath.concat(file.getName());
85  				if (match(relPath, includes, excludes, false)) {
86  					uploadFile(session, file, remoteDir + '/' + relPath);
87  				}
88  			} else {
89  				String relPath = currentRelPath.concat(file.getName()).concat(
90  						"/");
91  				if (match(relPath, includes, excludes, true)) {
92  					String nextDir = dir.concat(file.getName()).concat(
93  							File.separator);
94  					scanDir(session, nextDir, relPath, includes, excludes);
95  				}
96  			}
97  		}
98  	}
99  
100 	protected Boolean match(String path, List<String> includes,
101 			List<String> excludes, boolean matchStart) {
102 		for (String patternIn : includes) {
103 			boolean matchIn = matchStart ? pathMatcher.matchStart(patternIn,
104 					path) : pathMatcher.match(patternIn, path);
105 			if (matchIn) {
106 				// Could be included, check excludes
107 				boolean excluded = false;
108 				ex: for (String patternEx : excludes) {
109 					boolean matchEx = matchStart ? pathMatcher.matchStart(
110 							patternEx, path) : pathMatcher.match(patternEx,
111 							path);
112 
113 					if (matchEx) {
114 						excluded = true;
115 						break ex;
116 					}
117 				}
118 				if (!excluded)
119 					return true;
120 			}
121 		}
122 		return false;
123 	}
124 
125 	protected void uploadFile(Session session, File file, String remoteFile) {
126 		try {
127 			upload(session, new FileInputStream(file), file.length(), file
128 					.getPath(), file.toString(), remoteFile);
129 		} catch (FileNotFoundException e) {
130 			throw new SlcException("Cannot upload " + file, e);
131 		}
132 	}
133 
134 	protected void uploadResource(Session session, Resource resource) {
135 		String targetPath = remotePath != null ? remotePath : remoteDir + '/'
136 				+ resource.getFilename();
137 		try {
138 			File lFile = resource.getFile();
139 			uploadFile(session, lFile, targetPath);
140 		} catch (IOException e) {
141 			// no underlying file found
142 			// load the resource in memory before transferring it
143 			InputStream in = null;
144 			try {
145 				byte[] arr;
146 				String path;
147 				if (resource instanceof ByteArrayResource) {
148 					arr = ((ByteArrayResource) resource).getByteArray();
149 					path = "bytearray";
150 				} else {
151 					in = resource.getInputStream();
152 					ByteArrayOutputStream out = new ByteArrayOutputStream();
153 					IOUtils.copy(in, out);
154 					arr = out.toByteArray();
155 					path = resource.getURL().getPath();
156 					if (path.startsWith("/"))
157 						path = path.substring(1);
158 				}
159 				ByteArrayInputStream content = new ByteArrayInputStream(arr);
160 				upload(session, content, arr.length, path, resource.toString(),
161 						targetPath);
162 				arr = null;
163 			} catch (IOException e1) {
164 				throw new SlcException("Can not interpret resource "
165 						+ localResource, e1);
166 			} finally {
167 				IOUtils.closeQuietly(in);
168 				// no need to close byte arrays streams
169 			}
170 		}
171 	}
172 
173 	protected void upload(Session session, InputStream in, long size,
174 			String path, String sourceDesc, String remoteFile) {
175 		OutputStream channelOut;
176 		InputStream channelIn;
177 		try {
178 
179 			// exec 'scp -t rfile' remotely
180 			String command = "scp -p -t " + remoteFile;
181 			Channel channel = session.openChannel("exec");
182 			((ChannelExec) channel).setCommand(command);
183 
184 			// get I/O streams for remote scp
185 			channelOut = channel.getOutputStream();
186 			channelIn = channel.getInputStream();
187 
188 			channel.connect();
189 			checkAck(channelIn);
190 
191 			// send "C0644 filesize filename", where filename should not include
192 			// '/'
193 			long filesize = size;
194 			command = "C0644 " + filesize + " ";
195 			int index = path.lastIndexOf('/');
196 			if (index > 0) {
197 				command += path.substring(index + 1);
198 			} else {
199 				command += path;
200 			}
201 			command += "\n";
202 
203 			channelOut.write(command.getBytes());
204 			channelOut.flush();
205 			checkAck(channelIn);
206 
207 			if (log.isTraceEnabled())
208 				log.debug("Start copy of " + sourceDesc + " to " + remoteFile
209 						+ " on " + getSshTarget() + "...");
210 
211 			final long oneMB = 1024l;// in KB
212 			final long tenMB = 10 * oneMB;// in KB
213 
214 			// send a content of lfile
215 			byte[] buf = new byte[1024];
216 			long cycleCount = 0;
217 			long nbrOfBytes = 0;
218 			while (true) {
219 				int len = in.read(buf, 0, buf.length);
220 				if (len <= 0)
221 					break;
222 				channelOut.write(buf, 0, len); // out.flush();
223 				nbrOfBytes = nbrOfBytes + len;
224 				if (((cycleCount % oneMB) == 0) && cycleCount != 0)// each 1 MB
225 					System.out.print('#');
226 				if (((cycleCount % (tenMB)) == 0) && cycleCount != 0)// each 10
227 					// MB
228 					System.out.print(" - " + cycleCount / tenMB + "0 MB\n");
229 				cycleCount++;
230 			}
231 			// send '\0'
232 			buf[0] = 0;
233 			channelOut.write(buf, 0, 1);
234 			channelOut.flush();
235 			checkAck(channelIn);
236 
237 			if (log.isDebugEnabled())
238 				log.debug("Transferred to " + remoteFile + " ("
239 						+ sizeDesc(nbrOfBytes) + ") on " + getSshTarget()
240 						+ " from " + sourceDesc);
241 
242 			IOUtils.closeQuietly(channelOut);
243 
244 			channel.disconnect();
245 		} catch (Exception e) {
246 			throw new SlcException("Cannot copy " + path + " to " + remoteFile,
247 					e);
248 		} finally {
249 			IOUtils.closeQuietly(in);
250 		}
251 	}
252 
253 	protected String sizeDesc(Long nbrOfBytes) {
254 		if (nbrOfBytes < 1024)
255 			return nbrOfBytes + " B";
256 		else if (nbrOfBytes < 1024 * 1024)
257 			return (nbrOfBytes / 1024) + " KB";
258 		else
259 			return nbrOfBytes / (1024 * 1024) + " MB";
260 	}
261 
262 	public void setLocalResource(Resource localFile) {
263 		this.localResource = localFile;
264 	}
265 
266 	public void setRemotePath(String remoteFile) {
267 		this.remotePath = remoteFile;
268 	}
269 
270 	public void setDir(String dir) {
271 		this.dir = dir;
272 	}
273 
274 	public void setRemoteDir(String remoteDir) {
275 		this.remoteDir = remoteDir;
276 	}
277 
278 	public void setIncludes(List<String> includes) {
279 		this.includes = includes;
280 	}
281 
282 	public void setExcludes(List<String> excludes) {
283 		this.excludes = excludes;
284 	}
285 
286 	public void setPathMatcher(PathMatcher pathMatcher) {
287 		this.pathMatcher = pathMatcher;
288 	}
289 
290 }