1 package fr.jayasoft.ivy.repository.vsftp; 2 3 import java.io.File ; 4 import java.io.IOException ; 5 import java.io.InputStreamReader ; 6 import java.io.PrintWriter ; 7 import java.io.Reader ; 8 import java.text.SimpleDateFormat ; 9 import java.util.ArrayList ; 10 import java.util.List ; 11 import java.util.Locale ; 12 import java.util.regex.Pattern ; 13 14 import fr.jayasoft.ivy.Ivy; 15 import fr.jayasoft.ivy.IvyContext; 16 import fr.jayasoft.ivy.event.IvyEvent; 17 import fr.jayasoft.ivy.event.IvyListener; 18 import fr.jayasoft.ivy.event.resolve.EndResolveEvent; 19 import fr.jayasoft.ivy.repository.AbstractRepository; 20 import fr.jayasoft.ivy.repository.BasicResource; 21 import fr.jayasoft.ivy.repository.Resource; 22 import fr.jayasoft.ivy.repository.TransferEvent; 23 import fr.jayasoft.ivy.util.IvyThread; 24 import fr.jayasoft.ivy.util.Message; 25 26 40 public class VsftpRepository extends AbstractRepository { 41 private static final String PROMPT = "vsftp> "; 42 43 private static final SimpleDateFormat FORMAT = new SimpleDateFormat ("MMM dd, yyyy HH:mm", Locale.US); 44 45 private String _host; 46 private String _username; 47 private String _authentication = "gssapi"; 48 49 private Reader _in; 50 private Reader _err; 51 private PrintWriter _out; 52 53 private volatile StringBuffer _errors = new StringBuffer (); 54 55 private long _readTimeout = 30000; 56 57 private long _reuseConnection = 5 * 60 * 1000; 59 private volatile long _lastCommand; 60 61 private volatile boolean _inCommand; 62 63 private Process _process; 64 65 private Thread _connectionCleaner; 66 67 private Thread _errorsReader; 68 private volatile long _errorsLastUpdateTime; 69 70 private Ivy _ivy = null; 71 72 public Resource getResource(String source) throws IOException { 73 initIvy(); 74 return new VsftpResource(this, source); 75 } 76 77 private void initIvy() { 78 _ivy = IvyContext.getContext().getIvy(); 79 } 80 81 protected Resource getInitResource(String source) throws IOException { 82 try { 83 return lslToResource(source, sendCommand("ls -l "+source, true, true)); 84 } catch (IOException ex) { 85 cleanup(ex); 86 throw ex; 87 } finally { 88 cleanup(); 89 } 90 } 91 92 public void get(final String source, File destination) throws IOException { 93 initIvy(); 94 try { 95 fireTransferInitiated(getResource(source), TransferEvent.REQUEST_GET); 96 File destDir = destination.getParentFile(); 97 if (destDir != null) { 98 sendCommand("lcd "+destDir.getAbsolutePath()); 99 } 100 if (destination.exists()) { 101 destination.delete(); 102 } 103 104 int index = source.lastIndexOf('/'); 105 String srcName = index == -1?source:source.substring(index+1); 106 final File to = destDir == null ? new File (srcName):new File (destDir, srcName); 107 108 final IOException ex[] = new IOException [1]; 109 Thread get = new IvyThread() { 110 public void run() { 111 initContext(); 112 try { 113 sendCommand("get "+source, getExpectedDownloadMessage(source, to), 0); 114 } catch (IOException e) { 115 ex[0] = e; 116 } 117 } 118 }; 119 get.start(); 120 121 long prevLength = 0; 122 long lastUpdate = System.currentTimeMillis(); 123 long timeout = _readTimeout; 124 while (get.isAlive()) { 125 checkInterrupted(); 126 long length = to.exists()?to.length():0; 127 if (length > prevLength) { 128 fireTransferProgress(length - prevLength); 129 lastUpdate = System.currentTimeMillis(); 130 prevLength = length; 131 } else { 132 if (System.currentTimeMillis() - lastUpdate > timeout) { 133 Message.verbose("download hang for more than "+timeout+"ms. Interrupting."); 134 get.interrupt(); 135 if (to.exists()) to.delete(); 136 throw new IOException (source+" download timeout from "+getHost()); 137 } 138 } 139 try { 140 get.join(100); 141 } catch (InterruptedException e) { 142 if (to.exists()) to.delete(); 143 return; 144 } 145 } 146 if (ex[0] != null) { 147 if (to.exists()) to.delete(); 148 throw ex[0]; 149 } 150 151 to.renameTo(destination); 152 fireTransferCompleted(destination.length()); 153 } catch (IOException ex) { 154 fireTransferError(ex); 155 cleanup(ex); 156 throw ex; 157 } finally { 158 cleanup(); 159 } 160 } 161 162 public List list(String parent) throws IOException { 163 initIvy(); 164 try { 165 if (!parent.endsWith("/")) { 166 parent = parent+"/"; 167 } 168 String response = sendCommand("ls -l "+parent, true, true); 169 if (response.startsWith("ls")) { 170 return null; 171 } 172 String [] lines = response.split("\n"); 173 List ret = new ArrayList (lines.length); 174 for (int i = 0; i < lines.length; i++) { 175 while (lines[i].endsWith("\r") || lines[i].endsWith("\n")) { 176 lines[i] = lines[i].substring(0, lines[i].length() -1); 177 } 178 if (lines[i].trim().length() != 0) { 179 ret.add(parent+lines[i].substring(lines[i].lastIndexOf(' ')+1)); 180 } 181 } 182 return ret; 183 } catch (IOException ex) { 184 cleanup(ex); 185 throw ex; 186 } finally { 187 cleanup(); 188 } 189 } 190 191 public void put(File source, String destination, boolean overwrite) throws IOException { 192 initIvy(); 193 try { 194 if (getResource(destination).exists()) { 195 if (overwrite) { 196 sendCommand("rm "+destination, getExpectedRemoveMessage(destination)); 197 } else { 198 return; 199 } 200 } 201 int index = destination.lastIndexOf('/'); 202 String destDir = null; 203 if (index != -1) { 204 destDir = destination.substring(0, index); 205 mkdirs(destDir); 206 sendCommand("cd "+destDir); 207 } 208 String to = destDir != null ? destDir+"/"+source.getName():source.getName(); 209 sendCommand("put "+source.getAbsolutePath(), getExpectedUploadMessage(source, to), 0); 210 sendCommand("mv "+to+" "+destination); 211 } catch (IOException ex) { 212 cleanup(ex); 213 throw ex; 214 } finally { 215 cleanup(); 216 } 217 } 218 219 220 private void mkdirs(String destDir) throws IOException { 221 if (dirExists(destDir)) { 222 return; 223 } 224 if (destDir.endsWith("/")) { 225 destDir = destDir.substring(0, destDir.length() - 1); 226 } 227 int index = destDir.lastIndexOf('/'); 228 if (index != - 1) { 229 mkdirs(destDir.substring(0, index));; 230 } 231 sendCommand("mkdir "+destDir); 232 } 233 234 private boolean dirExists(String dir) throws IOException { 235 return !sendCommand("ls "+dir, true).startsWith("ls: "); 236 } 237 238 protected String sendCommand(String command) throws IOException { 239 return sendCommand(command, false, _readTimeout); 240 } 241 242 protected void sendCommand(String command, Pattern expectedResponse) throws IOException { 243 sendCommand(command, expectedResponse, _readTimeout); 244 } 245 246 257 protected void sendCommand(String command, Pattern expectedResponse, long timeout) throws IOException { 258 String response = sendCommand(command, true, timeout); 259 if (!expectedResponse.matcher(response).matches()) { 260 Message.debug("invalid response from server:"); 261 Message.debug("expected: '"+expectedResponse+"'"); 262 Message.debug("was: '"+response+"'"); 263 throw new IOException (response); 264 } 265 } 266 protected String sendCommand(String command, boolean sendErrorAsResponse) throws IOException { 267 return sendCommand(command, sendErrorAsResponse, _readTimeout); 268 } 269 270 protected String sendCommand(String command, boolean sendErrorAsResponse, boolean single) throws IOException { 271 return sendCommand(command, sendErrorAsResponse, single, _readTimeout); 272 } 273 274 protected String sendCommand(String command, boolean sendErrorAsResponse, long timeout) throws IOException { 275 return sendCommand(command, sendErrorAsResponse, false, timeout); 276 } 277 278 protected String sendCommand(String command, boolean sendErrorAsResponse, boolean single, long timeout) throws IOException { 279 single = false; 281 checkInterrupted(); 282 _inCommand = true; 283 _errorsLastUpdateTime = 0; 284 synchronized (this) { 285 if (!single || _in != null) { 286 ensureConnectionOpened(); 287 Message.debug("sending command '"+command+"' to "+getHost()); 288 updateLastCommandTime(); 289 _out.println(command); 290 _out.flush(); 291 } else { 292 sendSingleCommand(command); 293 } 294 } 295 296 try { 297 return readResponse(sendErrorAsResponse, timeout); 298 } finally { 299 _inCommand = false; 300 if (single) { 301 closeConnection(); 302 } 303 } 304 } 305 306 protected String readResponse(boolean sendErrorAsResponse) throws IOException { 307 return readResponse(sendErrorAsResponse, _readTimeout); 308 } 309 310 protected synchronized String readResponse(final boolean sendErrorAsResponse, long timeout) throws IOException { 311 final StringBuffer response = new StringBuffer (); 312 final IOException [] exc = new IOException [1]; 313 final boolean[] done = new boolean[1]; 314 Runnable r = new Runnable () { 315 public void run() { 316 synchronized (VsftpRepository.this) { 317 try { 318 int c; 319 boolean getPrompt = false; 320 for (int attempts = 0; !getPrompt && attempts < 5; attempts++) { 322 while ((c = _in.read()) != -1) { 323 attempts = 0; response.append((char)c); 325 if (response.length() >= PROMPT.length() 326 && response.substring(response.length() - PROMPT.length(), response.length()).equals(PROMPT)) { 327 response.setLength(response.length() - PROMPT.length()); 328 getPrompt = true; 329 break; 330 } 331 } 332 if (!getPrompt) { 333 try { 334 Thread.sleep(50); 335 } catch (InterruptedException e) { 336 break; 337 } 338 } 339 } 340 if (getPrompt) { 341 if (_errorsLastUpdateTime == 0) { 343 _errorsLastUpdateTime = _lastCommand; 345 } 346 347 while ((System.currentTimeMillis() - _errorsLastUpdateTime) < 50) { 348 try { 349 Thread.sleep(30); 350 } catch (InterruptedException e) { 351 break; 352 } 353 } 354 } 355 if (_errors.length() > 0) { 356 if (sendErrorAsResponse) { 357 response.append(_errors); 358 _errors.setLength(0); 359 } else { 360 throw new IOException (chomp(_errors).toString()); 361 } 362 } 363 chomp(response); 364 done[0] = true; 365 } catch (IOException e) { 366 exc[0] = e; 367 } finally { 368 VsftpRepository.this.notify(); 369 } 370 } 371 } 372 }; 373 Thread reader = null; 374 if (timeout == 0) { 375 r.run(); 376 } else { 377 reader = new IvyThread(r); 378 reader.start(); 379 try { 380 wait(timeout); 381 } catch (InterruptedException e) { 382 } 383 } 384 updateLastCommandTime(); 385 if (exc[0] != null) { 386 throw exc[0]; 387 } else if (!done[0]) { 388 if (reader != null && reader.isAlive()) { 389 reader.interrupt(); 390 for (int i = 0; i<5 && reader.isAlive(); i++) { 391 try { 392 Thread.sleep(100); 393 } catch (InterruptedException e) { 394 break; 395 } 396 } 397 if (reader.isAlive()) { 398 reader.stop(); } 400 } 401 throw new IOException ("connection timeout to "+getHost()); 402 } else { 403 if ("Not connected.".equals(response)) { 404 Message.info("vsftp connection to "+getHost()+" reset"); 405 closeConnection(); 406 throw new IOException ("not connected to "+getHost()); 407 } 408 Message.debug("received response '"+response+"' from "+getHost()); 409 return response.toString(); 410 } 411 } 412 413 private synchronized void sendSingleCommand(String command) throws IOException { 414 exec(getSingleCommand(command)); 415 } 416 417 protected synchronized void ensureConnectionOpened() throws IOException { 418 if (_in == null) { 419 Message.verbose("connecting to "+getUsername()+"@"+getHost()+"... "); 420 String connectionCommand = getConnectionCommand(); 421 exec(connectionCommand); 422 423 try { 424 readResponse(false); 426 if (_reuseConnection > 0) { 427 _connectionCleaner = new IvyThread() { 428 public void run() { 429 initContext(); 430 try { 431 long sleep = 10; 432 while (_in != null && sleep > 0) { 433 sleep(sleep); 434 sleep = _reuseConnection - (System.currentTimeMillis() - _lastCommand); 435 if (_inCommand) { 436 sleep = sleep <= 0 ? _reuseConnection : sleep; 437 } 438 } 439 } catch (InterruptedException e) { 440 } 441 disconnect(); 442 } 443 }; 444 _connectionCleaner.start(); 445 } 446 447 if (_ivy != null) { 448 _ivy.addIvyListener(new IvyListener() { 449 public void progress(IvyEvent event) { 450 disconnect(); 451 event.getSource().removeIvyListener(this); 452 } 453 }, EndResolveEvent.NAME); 454 } 455 456 457 } catch (IOException ex) { 458 closeConnection(); 459 throw new IOException ("impossible to connect to "+getUsername()+"@"+getHost()+" using "+getAuthentication()+": "+ex.getMessage()); 460 } 461 Message.verbose("connected to "+getHost()); 462 } 463 } 464 465 private void updateLastCommandTime() { 466 _lastCommand = System.currentTimeMillis(); 467 } 468 469 private void exec(String command) throws IOException { 470 Message.debug("launching '"+command+"'"); 471 _process = Runtime.getRuntime().exec(command); 472 _in = new InputStreamReader (_process.getInputStream()); 473 _err = new InputStreamReader (_process.getErrorStream()); 474 _out = new PrintWriter (_process.getOutputStream()); 475 476 _errorsReader = new IvyThread() { 477 public void run() { 478 initContext(); 479 int c; 480 try { 481 while (_err != null && (c = _err.read()) != -1) { 482 _errors.append((char)c); 483 _errorsLastUpdateTime = System.currentTimeMillis(); 484 } 485 } catch (IOException e) { 486 } 487 } 488 }; 489 _errorsReader.start(); 490 } 491 492 493 private void checkInterrupted() { 494 if (_ivy != null) { 495 _ivy.checkInterrupted(); 496 } 497 } 498 499 502 private void cleanup(Exception ex) { 503 if (ex.getMessage().equals("connection timeout to "+getHost())) { 504 closeConnection(); 505 } else { 506 disconnect(); 507 } 508 } 509 510 513 private void cleanup() { 514 if (_reuseConnection == 0) { 515 disconnect(); 516 } 517 } 518 519 public synchronized void disconnect() { 520 if (_in != null) { 521 Message.verbose("disconnecting from "+getHost()+"... "); 522 try { 523 sendCommand("exit", false, 300); 524 } catch (IOException e) { 525 } finally { 526 closeConnection(); 527 Message.verbose("disconnected of "+getHost()); 528 } 529 } 530 } 531 532 private synchronized void closeConnection() { 533 if (_connectionCleaner != null) { 534 _connectionCleaner.interrupt(); 535 } 536 if (_errorsReader != null) { 537 _errorsReader.interrupt(); 538 } 539 try { 540 _process.destroy(); 541 } catch (Exception ex) {} 542 try { 543 _in.close(); 544 } catch (Exception e) {} 545 try { 546 _err.close(); 547 } catch (Exception e) {} 548 try { 549 _out.close(); 550 } catch (Exception e) {} 551 552 _connectionCleaner = null; 553 _errorsReader = null; 554 _process = null; 555 _in = null; 556 _out = null; 557 _err = null; 558 Message.debug("connection to "+getHost()+" closed"); 559 } 560 561 567 protected Resource lslToResource(String file, String responseLine) { 568 if (responseLine == null || responseLine.startsWith("ls")) { 569 return new BasicResource(file, false, 0, 0, false); 570 } else { 571 String [] parts = responseLine.split("\\s+"); 572 if (parts.length != 9) { 573 Message.debug("unrecognized ls format: "+responseLine); 574 return new BasicResource(file, false, 0, 0, false); 575 } else { 576 try { 577 long contentLength = Long.parseLong(parts[3]); 578 String date = parts[4]+" "+parts[5]+" "+parts[6]+" "+parts[7]; 579 return new BasicResource(file, true, contentLength, FORMAT.parse(date).getTime(), false); 580 } catch (Exception ex) { 581 Message.warn("impossible to parse server response: "+responseLine+": "+ex); 582 return new BasicResource(file, false, 0, 0, false); 583 } 584 } 585 } 586 } 587 588 protected String getSingleCommand(String command) { 589 return "vsh -noprompt -auth "+_authentication+" "+_username+"@"+_host+" "+command; 590 } 591 592 protected String getConnectionCommand() { 593 return "vsftp -noprompt -auth "+_authentication+" "+_username+"@"+_host; 594 } 595 596 protected Pattern getExpectedDownloadMessage(String source, File to) { 597 return Pattern.compile("Downloading "+to.getName()+" from [^\\s]+"); 598 } 599 600 protected Pattern getExpectedRemoveMessage(String destination) { 601 return Pattern.compile("Removing [^\\s]+"); 602 } 603 604 protected Pattern getExpectedUploadMessage(File source, String to) { 605 return Pattern.compile("Uploading "+source.getName()+" to [^\\s]+"); 606 } 607 608 609 public String getAuthentication() { 610 return _authentication; 611 } 612 613 public void setAuthentication(String authentication) { 614 _authentication = authentication; 615 } 616 617 public String getHost() { 618 return _host; 619 } 620 621 public void setHost(String host) { 622 _host = host; 623 } 624 625 public String getUsername() { 626 return _username; 627 } 628 629 public void setUsername(String username) { 630 _username = username; 631 } 632 633 private static StringBuffer chomp(StringBuffer str) { 634 if (str == null || str.length() == 0) { 635 return str; 636 } 637 while ("\n".equals(str.substring(str.length() - 1)) || "\r".equals(str.substring(str.length() - 1))) { 638 str.setLength(str.length() - 1); 639 } 640 return str; 641 } 642 643 public String toString() { 644 return getName()+" "+getUsername()+"@"+getHost()+" ("+getAuthentication()+")"; 645 } 646 647 655 public void setReuseConnection(long time) { 656 _reuseConnection = time; 657 } 658 659 public long getReadTimeout() { 660 return _readTimeout; 661 } 662 663 public void setReadTimeout(long readTimeout) { 664 _readTimeout = readTimeout; 665 } 666 } 667 | Popular Tags |