/******************************************************************************* * Copyright (c) 2006-2013, Cloudsmith Inc. * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the copyright holder * listed above, as the Initial Contributor under such license. The text of * such license is available at www.eclipse.org. ******************************************************************************/ package org.eclipse.buckminster.download.internal; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.net.URL; import java.util.Date; import org.eclipse.buckminster.download.Messages; import org.eclipse.buckminster.runtime.Buckminster; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.BuckminsterPreferences; import org.eclipse.buckminster.runtime.FileInfoBuilder; import org.eclipse.buckminster.runtime.IFileInfo; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.ecf.core.security.IConnectContext; import org.eclipse.ecf.filetransfer.FileTransferJob; import org.eclipse.ecf.filetransfer.IFileTransferListener; import org.eclipse.ecf.filetransfer.IIncomingFileTransfer; import org.eclipse.ecf.filetransfer.IRetrieveFileTransferContainerAdapter; import org.eclipse.ecf.filetransfer.IncomingFileTransferException; import org.eclipse.ecf.filetransfer.UserCancelledException; import org.eclipse.ecf.filetransfer.events.IFileTransferEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveDataEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveDoneEvent; import org.eclipse.ecf.filetransfer.events.IIncomingFileTransferReceiveStartEvent; import org.eclipse.ecf.filetransfer.identity.FileIDFactory; import org.eclipse.ecf.filetransfer.identity.IFileID; import org.eclipse.osgi.util.NLS; /** * @author Thomas Hallgren */ public class FileReader extends FileTransferJob implements IFileTransferListener { private boolean closeStreamWhenFinished = false; private Exception exception; private IFileInfo fileInfo; private long lastProgressCount; private long lastStatsCount; private IProgressMonitor monitor; private boolean onlyGetInfo = false; private OutputStream outputStream; private ProgressStatistics statistics; private final int connectionRetryCount; private final long connectionRetryDelay; private final IConnectContext connectContext; /** * Create a new FileReader that will retry failed connection attempts and * sleep some amount of time between each attempt. * * @param connectionRetryCount * The number of times to retry the connection. Set to zero to * fail on first attempt. * @param connectionRetryDelay * The number of milliseconds to sleep between each attempt. */ public FileReader(IConnectContext connectContext) { super(Messages.URL_reader); // Hide this job. setSystem(true); setUser(false); this.connectionRetryCount = BuckminsterPreferences.getConnectionRetryCount(); this.connectionRetryDelay = BuckminsterPreferences.getConnectionRetryDelay() * 1000L; this.connectContext = connectContext; } public IFileInfo getLastFileInfo() { return fileInfo; } @Override public synchronized void handleTransferEvent(IFileTransferEvent event) { if (event instanceof IIncomingFileTransferReceiveStartEvent) { IIncomingFileTransfer source = ((IIncomingFileTransferEvent) event).getSource(); try { FileInfoBuilder fi = new FileInfoBuilder(); Date lastModified = source.getRemoteLastModified(); if (lastModified != null) fi.setLastModified(lastModified.getTime()); fi.setName(source.getRemoteFileName()); fi.setSize(source.getFileLength()); fileInfo = fi; if (onlyGetInfo) { source.cancel(); } else ((IIncomingFileTransferReceiveStartEvent) event).receive(outputStream, this); } catch (IOException e) { exception = e; return; } if (monitor != null) { long fileLength = source.getFileLength(); statistics = new ProgressStatistics(source.getRemoteFileName(), fileLength); monitor.beginTask(null, 1000); monitor.subTask(statistics.report()); lastStatsCount = 0; lastProgressCount = 0; } } else if (event instanceof IIncomingFileTransferReceiveDataEvent) { IIncomingFileTransfer source = ((IIncomingFileTransferEvent) event).getSource(); if (monitor != null) { if (monitor.isCanceled()) { source.cancel(); return; } long br = source.getBytesReceived(); long count = br - lastStatsCount; lastStatsCount = br; statistics.increase(count); if (statistics.shouldReport()) { count = br - lastProgressCount; lastProgressCount = br; monitor.subTask(statistics.report()); monitor.worked((int) (1000 * count / statistics.getTotal())); } } } else if (event instanceof IIncomingFileTransferReceiveDoneEvent) { if (closeStreamWhenFinished) IOUtils.close(outputStream); if (exception == null) { exception = ((IIncomingFileTransferReceiveDoneEvent) event).getException(); if (exception instanceof UserCancelledException) exception = null; } } } public InputStream read(URL url) throws CoreException, FileNotFoundException { final PipedInputStream input = new PipedInputStream(); PipedOutputStream output; try { output = new PipedOutputStream(input); } catch (IOException e) { throw BuckminsterException.wrap(e); } Buckminster.getLogger().debug("Downloading %s", url); //$NON-NLS-1$ final IProgressMonitor cancellationMonitor = new NullProgressMonitor(); sendRetrieveRequest(url, output, true, false, cancellationMonitor); return new InputStream() { @Override public int available() throws IOException { checkException(); return input.available(); } private void checkException() throws IOException { if (exception == null) return; IOException e; Throwable t = BuckminsterException.unwind(exception); if (t instanceof IOException) e = (IOException) t; else { e = new IOException(t.getMessage()); e.initCause(t); } throw e; } @Override public void close() throws IOException { cancellationMonitor.setCanceled(true); IOUtils.close(input); checkException(); } @Override public void mark(int readlimit) { input.mark(readlimit); } @Override public boolean markSupported() { return input.markSupported(); } @Override public int read() throws IOException { checkException(); return input.read(); } @Override public int read(byte b[]) throws IOException { checkException(); return input.read(b); } @Override public int read(byte b[], int off, int len) throws IOException { checkException(); return input.read(b, off, len); } @Override public void reset() throws IOException { checkException(); input.reset(); } @Override public long skip(long n) throws IOException { checkException(); return input.skip(n); } }; } public IFileInfo readInfo(URL url) throws CoreException, FileNotFoundException { sendRetrieveRequest(url, null, false, true, null); return getLastFileInfo(); } public void readInto(URL url, OutputStream out, IProgressMonitor mon) throws CoreException, FileNotFoundException { try { sendRetrieveRequest(url, out, false, false, mon); join(); } catch (InterruptedException e) { mon.setCanceled(true); throw new OperationCanceledException(); } finally { if (mon != null) { if (statistics == null) // // Monitor was never started. See to that it's balanced // mon.beginTask(null, 1); else statistics = null; mon.done(); } } } protected void sendRetrieveRequest(URL url, OutputStream out, boolean closeWhenFinished, boolean onlyInfo, IProgressMonitor mon) throws CoreException, FileNotFoundException { IRetrieveFileTransferContainerAdapter adapter = Activator.getDefault().createRetrieveFileTransfer(); adapter.setConnectContextForAuthentication(connectContext); exception = null; closeStreamWhenFinished = closeWhenFinished; onlyGetInfo = onlyInfo; fileInfo = null; statistics = null; lastProgressCount = 0L; lastStatsCount = 0L; monitor = mon; outputStream = out; for (int retryCount = 0;;) { if (monitor != null && monitor.isCanceled()) throw new OperationCanceledException(); try { IFileID fileID = FileIDFactory.getDefault().createFileID(adapter.getRetrieveNamespace(), url); adapter.sendRetrieveRequest(fileID, null, this, null); } catch (IncomingFileTransferException e) { exception = e; } if (exception != null) { Throwable t; if (exception instanceof IncomingFileTransferException && ((IncomingFileTransferException) exception).getErrorCode() == 404) t = new FileNotFoundException(exception.getMessage()); else { t = BuckminsterException.unwind(exception); while (t instanceof CoreException) { Throwable t2 = ((CoreException) t).getStatus().getException(); if (t2 == null) throw (CoreException) t; t = t2; } } if (t instanceof FileNotFoundException) // // Connection succeeded but the target doesn't exist // throw (FileNotFoundException) t; if (t instanceof IOException && retryCount < connectionRetryCount) { // TODO: Retry only certain exceptions or filter out // some exceptions not worth retrying // ++retryCount; exception = null; try { Buckminster.getLogger().warning( NLS.bind(Messages.connection_to_0_failed_on_1_retry_attempt_2, new String[] { url.toString(), t.getMessage(), String.valueOf(retryCount) })); Thread.sleep(connectionRetryDelay); continue; } catch (InterruptedException e) { } } throw BuckminsterException.wrap(exception); } break; } } }