diff options
author | Henrik Lindberg | 2009-04-14 15:08:43 +0000 |
---|---|---|
committer | Henrik Lindberg | 2009-04-14 15:08:43 +0000 |
commit | 8f2c289ae949c30f5ceddef9679214d427ad859c (patch) | |
tree | 6e430b7d4c4fcc4de235c418e3b75a04bfdacf9d /bundles/org.eclipse.equinox.p2.repository | |
parent | b61bea3b11db7c7caa16502748b54ec6eec7e339 (diff) | |
download | rt.equinox.p2-8f2c289ae949c30f5ceddef9679214d427ad859c.tar.gz rt.equinox.p2-8f2c289ae949c30f5ceddef9679214d427ad859c.tar.xz rt.equinox.p2-8f2c289ae949c30f5ceddef9679214d427ad859c.zip |
https://bugs.eclipse.org/bugs/show_bug.cgi?id=266243 - make download of meta data repository index files resumable (resume functionality controled via property).
Also includes:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=271473 - include URI in download progress statistics messages
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.repository')
8 files changed, 346 insertions, 65 deletions
diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/CacheManager.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/CacheManager.java index 7062d79f8..109879e06 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/CacheManager.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/CacheManager.java @@ -7,6 +7,7 @@ * * Contributors: * IBM Corporation - initial API and implementation + * henrik.lindberg@cloudsmith.com - resume of download *******************************************************************************/ package org.eclipse.equinox.internal.p2.metadata.repository; @@ -17,14 +18,13 @@ import java.util.*; import org.eclipse.core.runtime.*; import org.eclipse.ecf.filetransfer.UserCancelledException; import org.eclipse.equinox.internal.p2.core.helpers.*; -import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; -import org.eclipse.equinox.internal.p2.repository.RepositoryTransport; +import org.eclipse.equinox.internal.p2.repository.*; +import org.eclipse.equinox.internal.p2.repository.Activator; import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException; import org.eclipse.equinox.internal.provisional.p2.core.eventbus.IProvisioningEventBus; import org.eclipse.equinox.internal.provisional.p2.core.eventbus.SynchronousProvisioningListener; import org.eclipse.equinox.internal.provisional.p2.core.location.AgentLocation; -import org.eclipse.equinox.internal.provisional.p2.repository.IRepository; -import org.eclipse.equinox.internal.provisional.p2.repository.RepositoryEvent; +import org.eclipse.equinox.internal.provisional.p2.repository.*; import org.eclipse.osgi.util.NLS; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -38,6 +38,9 @@ import org.osgi.framework.ServiceReference; * was created for the repository. */ public class CacheManager { + private static final String PROP_RESUMABLE = "org.eclipse.equinox.p2.metadata.repository.resumable"; //$NON-NLS-1$ + private static final String RESUME_DEFAULT = "true"; //$NON-NLS-1$ + private static final String DOWNLOADING = "downloading"; //$NON-NLS-1$ private static SynchronousProvisioningListener busListener; private static final String JAR_EXTENSION = ".jar"; //$NON-NLS-1$ private static final String XML_EXTENSION = ".xml"; //$NON-NLS-1$ @@ -128,7 +131,7 @@ public class CacheManager { LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Server returned lastModified <= 0 for " + xmlLocation)); //$NON-NLS-1$ } catch (UserCancelledException e) { - throw new ProvisionException(Status.CANCEL_STATUS); + throw new OperationCanceledException(); } catch (FileNotFoundException e) { throw new FileNotFoundException(NLS.bind(Messages.CacheManager_Neither_0_nor_1_found, jarLocation, xmlLocation)); } catch (AuthenticationFailedException e) { @@ -149,23 +152,53 @@ public class CacheManager { return cacheFile; // Need to update cache + + // check if download is resumable cacheFile = new File(dataAreaFile, prefix + hashCode + useExtension); cacheFile.getParentFile().mkdirs(); - OutputStream metadata = new BufferedOutputStream(new FileOutputStream(cacheFile)); - IStatus result; + File resumeFile = new File(new File(cacheFile.getParentFile(), DOWNLOADING), cacheFile.getName()); + // if append should be performed or not + boolean append = false; + if (resumeFile.exists()) { + // the resume file can be too old + if (lastModifiedRemote != resumeFile.lastModified() || lastModifiedRemote <= 0) + safeDelete(resumeFile); + else { + if (resumeFile.renameTo(cacheFile)) + append = true; + else + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManager_CouldNotMove_0_ToCache, resumeFile))); + } + } + + StatefulStream metadata = new StatefulStream(new FileOutputStream(cacheFile, append)); + IStatus result = null; try { submonitor.setWorkRemaining(1000); - result = getTransport().download(remoteFile, metadata, submonitor.newChild(1000)); + // resume from cache file's length if in append mode + result = getTransport().download(remoteFile, metadata, append ? cacheFile.length() : -1, submonitor.newChild(1000)); + } catch (OperationCanceledException e) { + // need to pick up the status - a new operation canceled exception is thrown at the end + // as status will be CANCEL. + result = metadata.getStatus(); } finally { metadata.close(); + // result is null if a runtime error (other than OperationCanceledException) + // occurred, just delete the cache file (or a later attempt could fail + // with "premature end of file"). + if (result == null) + cacheFile.delete(); } - if (!result.isOK()) { - //don't leave a partial cache file lying around - // TODO: HENRIK Handle resume + if (result.isOK()) + return cacheFile; + + // if possible, keep a partial download to be resumed. + if (!makeResumeable(cacheFile, remoteFile, result)) cacheFile.delete(); - throw new ProvisionException(result); - } - return cacheFile; + + if (result.getSeverity() == IStatus.CANCEL || monitor.isCanceled()) + throw new OperationCanceledException(); + throw new ProvisionException(result); } finally { if (monitor != null) @@ -174,15 +207,76 @@ public class CacheManager { } /** - * Deletes the local cache file for the given repository + * Make cacheFile resumable and return true if it was possible. + * @param cacheFile - the partially downloaded file to make resumeable + * @param downloadStatus - the download status reported for the partial download + * @return true if the file was made resumable, false otherwise + */ + private boolean makeResumeable(File cacheFile, URI remoteFile, IStatus status) { + if (status == null || status.isOK() || cacheFile == null || !(status instanceof DownloadStatus)) + return false; + // check if resume feature is turned off + if (!isResumeEnabled()) + return false; + DownloadStatus downloadStatus = (DownloadStatus) status; + long currentLength = cacheFile.length(); + // if cache file does not exist, or nothing was written to it, there is nothing to resume + if (currentLength == 0L) + return false; + + long reportedSize = downloadStatus.getFileSize(); + long reportedModified = downloadStatus.getLastModified(); + + if (reportedSize == DownloadStatus.UNKNOWN_SIZE || reportedSize == 0L) { + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, NLS.bind(Messages.CacheManager_DownloadOf_0_NotResumable_NoFileSize, remoteFile))); + return false; + } + if (reportedModified <= 0) { + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, NLS.bind(Messages.CacheManager_DownloadOf_0_NotResumable_NoLastModified, remoteFile))); + return false; + } + + // if more than what was reported has been written something odd is going on, and we can't + // trust the reported size. + // There is a small chance that user canceled in the time window after the full download is seen, and the result is returned. In this + // case the reported and current lengths will be equal. + if (reportedSize < currentLength) { + LogHelper.log(new Status(IStatus.WARNING, Activator.ID, NLS.bind(Messages.CacheManager_DownloadOf_0_NotResumable_MoreReadThanSpecified, remoteFile))); + return false; + } + File resumeDir = new File(cacheFile.getParentFile(), DOWNLOADING); + if (!resumeDir.mkdir()) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManager_CanNotCreateDir_0_ForResumeOf_1, resumeDir, remoteFile))); + return false; + } + // move partial cache file to "downloading" directory + File resumeFile = new File(resumeDir, cacheFile.getName()); + if (!cacheFile.renameTo(resumeFile)) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManager_CouldNotMove_0_to_1_ForResumedDownload, cacheFile, resumeFile))); + return false; + } + // touch the file with remote modified time + if (!resumeFile.setLastModified(reportedModified)) { + LogHelper.log(new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManager_CouldNotSetLastModifiedOn_0_ForResume, resumeFile))); + return false; + } + return true; + } + + /** + * Deletes the local cache file(s) for the given repository * @param repositoryLocation */ void deleteCache(URI repositoryLocation) { for (Iterator it = knownPrefixes.iterator(); it.hasNext();) { String prefix = (String) it.next(); - File cacheFile = getCache(repositoryLocation, prefix); - if (cacheFile != null) - safeDelete(cacheFile); + File[] cacheFiles = getCacheFiles(repositoryLocation, prefix); + for (int i = 0; i < cacheFiles.length; i++) { + // delete the cache file if it exists + safeDelete(cacheFiles[i]); + // delete a resumable download if it exists + safeDelete(new File(new File(cacheFiles[i].getParentFile(), DOWNLOADING), cacheFiles[i].getName())); + } } } @@ -194,17 +288,39 @@ public class CacheManager { * the cache file does not exist. */ private File getCache(URI repositoryLocation, String prefix) { + File[] files = getCacheFiles(repositoryLocation, prefix); + if (files[0].exists()) + return files[0]; + return files[1].exists() ? files[1] : null; + + // AgentLocation agentLocation = (AgentLocation) ServiceHelper.getService(Activator.getContext(), AgentLocation.class.getName()); + // URL dataArea = agentLocation.getDataArea(Activator.ID + "/cache/"); //$NON-NLS-1$ + // File dataAreaFile = URLUtil.toFile(dataArea); + // int hashCode = computeHash(repositoryLocation); + // File cacheFile = new File(dataAreaFile, prefix + hashCode + JAR_EXTENSION); + // if (!cacheFile.exists()) { + // cacheFile = new File(dataAreaFile, prefix + hashCode + XML_EXTENSION); + // if (!cacheFile.exists()) + // return null; + // } + // return cacheFile; + } + + /** + * Determines the local file paths of the repository's potential cache files. + * @param repositoryLocation The location to compute the cache for + * @param prefix The prefix to use for this location + * @return A {@link File} array with the cache files for JAR and XML extensions. + */ + private File[] getCacheFiles(URI repositoryLocation, String prefix) { + File[] files = new File[2]; AgentLocation agentLocation = (AgentLocation) ServiceHelper.getService(Activator.getContext(), AgentLocation.class.getName()); URL dataArea = agentLocation.getDataArea(Activator.ID + "/cache/"); //$NON-NLS-1$ File dataAreaFile = URLUtil.toFile(dataArea); int hashCode = computeHash(repositoryLocation); - File cacheFile = new File(dataAreaFile, prefix + hashCode + JAR_EXTENSION); - if (!cacheFile.exists()) { - cacheFile = new File(dataAreaFile, prefix + hashCode + XML_EXTENSION); - if (!cacheFile.exists()) - return null; - } - return cacheFile; + files[0] = new File(dataAreaFile, prefix + hashCode + JAR_EXTENSION); + files[1] = new File(dataAreaFile, prefix + hashCode + XML_EXTENSION); + return files; } private Object getService(BundleContext ctx, String name) { @@ -266,4 +382,31 @@ public class CacheManager { busListener = null; } } + + public boolean isResumeEnabled() { + String resumeProp = System.getProperty(PROP_RESUMABLE, RESUME_DEFAULT); + return Boolean.valueOf(resumeProp).booleanValue(); + } + + /** + * IStateful implementation of BufferedOutputStream. Class is used to get the status from + * a download operation. + */ + private static class StatefulStream extends BufferedOutputStream implements IStateful { + private IStatus status; + + public StatefulStream(OutputStream stream) { + super(stream); + } + + public IStatus getStatus() { + + return status; + } + + public void setStatus(IStatus aStatus) { + status = aStatus; + } + + } } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/DownloadStatus.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/DownloadStatus.java index 024025e52..8b7811f41 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/DownloadStatus.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/DownloadStatus.java @@ -18,8 +18,11 @@ import org.eclipse.core.runtime.Status; */ public class DownloadStatus extends Status { public static final long UNKNOWN_RATE = -1; + public static final long UNKNOWN_SIZE = -1; private long speed = UNKNOWN_RATE; + private long fileSize = UNKNOWN_SIZE; + private long lastModified = 0; /** * Constructs a new DownloadStatus with the given attributes. @@ -35,6 +38,10 @@ public class DownloadStatus extends Status { super(severity, pluginId, message, exception); } + public DownloadStatus(int severity, String pluginId, int code, String message, Throwable exception) { + super(severity, pluginId, code, message, exception); + } + /** * Returns the download rate in bytes per second. If the rate is unknown, * @{link {@link #UNKNOWN_RATE}} is returned. @@ -51,4 +58,20 @@ public class DownloadStatus extends Status { public void setTransferRate(long rate) { this.speed = rate; } + + public void setFileSize(long aFileSize) { + fileSize = aFileSize; + } + + public long getFileSize() { + return fileSize; + } + + public void setLastModified(long timestamp) { + lastModified = timestamp; + } + + public long getLastModified() { + return lastModified; + } } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java index f9bf43efe..b4fd3b1e3 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/FileReader.java @@ -24,6 +24,7 @@ import org.eclipse.osgi.util.NLS; * @author henrik.lindberg@cloudsmith.com - adaption to 1.4 and to this p2 package */ public class FileReader extends FileTransferJob implements IFileTransferListener { + private static IFileReaderProbe testProbe; private boolean closeStreamWhenFinished = false; private Exception exception; private FileInfo fileInfo; @@ -35,6 +36,7 @@ public class FileReader extends FileTransferJob implements IFileTransferListener private final int connectionRetryCount; private final long connectionRetryDelay; private final IConnectContext connectContext; + private URI uri; /** * Create a new FileReader that will retry failed connection attempts and sleep some amount of time between each @@ -75,12 +77,13 @@ public class FileReader extends FileTransferJob implements IFileTransferListener if (theMonitor != null) { long fileLength = source.getFileLength(); - statistics = new ProgressStatistics(source.getRemoteFileName(), fileLength); + statistics = new ProgressStatistics(uri, source.getRemoteFileName(), fileLength); theMonitor.beginTask(null, 1000); theMonitor.subTask(statistics.report()); lastStatsCount = 0; lastProgressCount = 0; } + onStart(source); } else if (event instanceof IIncomingFileTransferReceiveDataEvent) { IIncomingFileTransfer source = ((IIncomingFileTransferEvent) event).getSource(); if (theMonitor != null) { @@ -101,12 +104,14 @@ public class FileReader extends FileTransferJob implements IFileTransferListener theMonitor.worked((int) (1000 * count / statistics.getTotal())); } } + onData(source); } else if (event instanceof IIncomingFileTransferReceiveDoneEvent) { if (closeStreamWhenFinished) hardClose(theOutputStream); if (exception == null) exception = ((IIncomingFileTransferReceiveDoneEvent) event).getException(); + onDone(((IIncomingFileTransferReceiveDoneEvent) event).getSource()); } } @@ -121,7 +126,7 @@ public class FileReader extends FileTransferJob implements IFileTransferListener RepositoryTracing.debug("Downloading {0}", url); //$NON-NLS-1$ final IProgressMonitor cancellationMonitor = new NullProgressMonitor(); - sendRetrieveRequest(url, output, true, cancellationMonitor); + sendRetrieveRequest(url, output, null, true, cancellationMonitor); return new InputStream() { public int available() throws IOException { @@ -185,27 +190,34 @@ public class FileReader extends FileTransferJob implements IFileTransferListener }; } - /** Only request info - * @deprecated REMOVE THIS METHOD - SHOULD USE BROWSE INSTEAD TO ONLY GET HEAD - ALSO REMOVE PARAMTER ONLYHEAD - * @param uri - * @return FileInfo - * @throws CoreException - * @throws FileNotFoundException - * @throws AuthenticationFailedException - */ - public FileInfo readInfo(URI uri) throws CoreException, FileNotFoundException, AuthenticationFailedException { - sendRetrieveRequest(uri, null, false, null); - return getLastFileInfo(); + // /** Only request info + // * @deprecated REMOVE THIS METHOD - SHOULD USE BROWSE INSTEAD TO ONLY GET HEAD - ALSO REMOVE PARAMTER ONLYHEAD + // * @param uri + // * @return FileInfo + // * @throws CoreException + // * @throws FileNotFoundException + // * @throws AuthenticationFailedException + // */ + // public FileInfo readInfo(URI uri) throws CoreException, FileNotFoundException, AuthenticationFailedException { + // sendRetrieveRequest(uri, null, false, null); + // return getLastFileInfo(); + // } + public void readInto(URI uri, OutputStream anOutputStream, IProgressMonitor monitor) // + throws CoreException, FileNotFoundException, AuthenticationFailedException { + readInto(uri, anOutputStream, -1, monitor); } - public void readInto(URI uri, OutputStream anOutputStream, IProgressMonitor monitor) // + public void readInto(URI uri, OutputStream anOutputStream, long startPos, IProgressMonitor monitor) // throws CoreException, FileNotFoundException, AuthenticationFailedException { try { - sendRetrieveRequest(uri, anOutputStream, false, monitor); + sendRetrieveRequest(uri, anOutputStream, (startPos != -1 ? new DownloadRange(startPos) : null), false, monitor); + join(); } catch (InterruptedException e) { monitor.setCanceled(true); throw new OperationCanceledException(); + // } catch (Throwable t) { + // t.printStackTrace(); } finally { if (monitor != null) { if (statistics == null) @@ -220,7 +232,7 @@ public class FileReader extends FileTransferJob implements IFileTransferListener } } - protected void sendRetrieveRequest(URI uri, OutputStream outputStream, boolean closeStreamOnFinish, // + protected void sendRetrieveRequest(URI uri, OutputStream outputStream, DownloadRange range, boolean closeStreamOnFinish, // IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException { IRetrieveFileTransferFactory factory = Activator.getDefault().getRetrieveFileTransferFactory(); @@ -239,6 +251,7 @@ public class FileReader extends FileTransferJob implements IFileTransferListener this.lastStatsCount = 0L; this.theMonitor = monitor; this.theOutputStream = outputStream; + this.uri = uri; for (int retryCount = 0;;) { if (monitor != null && monitor.isCanceled()) @@ -246,11 +259,17 @@ public class FileReader extends FileTransferJob implements IFileTransferListener try { IFileID fileID = FileIDFactory.getDefault().createFileID(adapter.getRetrieveNamespace(), uri.toString()); - adapter.sendRetrieveRequest(fileID, this, null); + if (range != null) + adapter.sendRetrieveRequest(fileID, range, this, null); + else + adapter.sendRetrieveRequest(fileID, this, null); } catch (IncomingFileTransferException e) { exception = e; } catch (FileCreateException e) { exception = e; + } catch (Throwable t) { + if (exception != null) + exception.printStackTrace(); } // note that 'exception' could have been captured in a callback @@ -309,4 +328,48 @@ public class FileReader extends FileTransferJob implements IFileTransferListener } } + private static class DownloadRange implements IFileRangeSpecification { + + private long startPosition; + + public DownloadRange(long startPos) { + startPosition = startPos; + } + + public long getEndPosition() { + return -1; + } + + public long getStartPosition() { + return startPosition; + } + + } + + private void onDone(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onDone(this, source, theMonitor); + } + + private void onStart(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onStart(this, source, theMonitor); + } + + private void onData(IIncomingFileTransfer source) { + if (testProbe != null) + testProbe.onData(this, source, theMonitor); + } + + public static void setTestProbe(IFileReaderProbe probe) { + testProbe = probe; + } + + public interface IFileReaderProbe { + public void onStart(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + + public void onData(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + + public void onDone(FileReader reader, IIncomingFileTransfer source, IProgressMonitor monitor); + } } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Messages.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Messages.java index e15d1a93e..0fb7714db 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Messages.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/Messages.java @@ -66,7 +66,9 @@ public class Messages extends NLS { public static String TransportErrorTranslator_UnknownHost; public static String fetching_0_1_at_2; + public static String fetching_0_from_1_2_at_3; public static String fetching_0_1_of_2_at_3; + public static String fetching_0_from_1_2_of_3_at_4; public static String connection_to_0_failed_on_1_retry_attempt_2; public static String FileTransport_reader; diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/ProgressStatistics.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/ProgressStatistics.java index f34a6c17f..786209b96 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/ProgressStatistics.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/ProgressStatistics.java @@ -7,6 +7,7 @@ ******************************************************************************/ package org.eclipse.equinox.internal.p2.repository; +import java.net.URI; import java.text.NumberFormat; import java.util.*; import java.util.Map.Entry; @@ -55,7 +56,9 @@ public class ProgressStatistics { private long m_recentSpeedMapKey; - public ProgressStatistics(String fileName, long total) { + private URI m_uri; + + public ProgressStatistics(URI uri, String fileName, long total) { m_startTime = System.currentTimeMillis(); m_fileName = fileName; @@ -66,6 +69,7 @@ public class ProgressStatistics { m_reportInterval = DEFAULT_REPORT_INTERVAL; m_recentSpeedMap = new TreeMap(); m_recentSpeedMapKey = 0L; + m_uri = uri; } public long getAverageSpeed() { @@ -121,7 +125,7 @@ public class ProgressStatistics { } public synchronized String report() { - return m_total != -1 ? NLS.bind(Messages.fetching_0_1_of_2_at_3, new String[] {m_fileName, convert(m_current), convert(m_total), convert(getRecentSpeed())}) : NLS.bind(Messages.fetching_0_1_at_2, new String[] {m_fileName, convert(m_current), convert(getRecentSpeed())}); + return m_total != -1 ? NLS.bind(Messages.fetching_0_from_1_2_of_3_at_4, new String[] {m_fileName, m_uri.toString(), convert(m_current), convert(m_total), convert(getRecentSpeed())}) : NLS.bind(Messages.fetching_0_from_1_2_at_3, new String[] {m_fileName, m_uri.toString(), convert(m_current), convert(getRecentSpeed())}); } public void setReportInterval(int reportInterval) { diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatus.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatus.java index 36a511ec5..434ac77e6 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatus.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryStatus.java @@ -14,7 +14,6 @@ package org.eclipse.equinox.internal.p2.repository; import java.io.FileNotFoundException; import java.net.*; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; import org.eclipse.ecf.core.identity.IDCreateException; import org.eclipse.ecf.filetransfer.IncomingFileTransferException; import org.eclipse.equinox.internal.provisional.p2.core.ProvisionException; @@ -108,24 +107,24 @@ public class RepositoryStatus { } } - public static IStatus forStatus(IStatus original, URI toDownload) { + public static DownloadStatus forStatus(IStatus original, URI toDownload) { Throwable t = original.getException(); return forException(t, toDownload); } - public static IStatus forException(Throwable t, URI toDownload) { + public static DownloadStatus forException(Throwable t, URI toDownload) { if (t instanceof FileNotFoundException || (t instanceof IncomingFileTransferException && ((IncomingFileTransferException) t).getErrorCode() == 404)) - return new Status(IStatus.ERROR, Activator.ID, ProvisionException.ARTIFACT_NOT_FOUND, NLS.bind(Messages.artifact_not_found, toDownload), t); + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.ARTIFACT_NOT_FOUND, NLS.bind(Messages.artifact_not_found, toDownload), t); if (t instanceof ConnectException) - return new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, NLS.bind(Messages.TransportErrorTranslator_UnableToConnectToRepository_0, toDownload), t); + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_READ, NLS.bind(Messages.TransportErrorTranslator_UnableToConnectToRepository_0, toDownload), t); if (t instanceof UnknownHostException) - return new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_UnknownHost, toDownload), t); + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_UnknownHost, toDownload), t); if (t instanceof IDCreateException) { IStatus status = ((IDCreateException) t).getStatus(); if (status != null && status.getException() != null) t = status.getException(); - return new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_MalformedRemoteFileReference, toDownload), t); + return new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_INVALID_LOCATION, NLS.bind(Messages.TransportErrorTranslator_MalformedRemoteFileReference, toDownload), t); } if (t instanceof IncomingFileTransferException) { int code = ((IncomingFileTransferException) t).getErrorCode(); @@ -136,12 +135,12 @@ public class RepositoryStatus { provisionCode = ProvisionException.REPOSITORY_FAILED_AUTHENTICATION; else if (code == 404) provisionCode = ProvisionException.ARTIFACT_NOT_FOUND; - return new Status(IStatus.ERROR, Activator.ID, provisionCode, // + return new DownloadStatus(IStatus.ERROR, Activator.ID, provisionCode, // codeToMessage(code, toDownload.toString()), t); } } // Add more specific translation here - return new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.io_failedRead, toDownload), t); + return new DownloadStatus(IStatus.ERROR, Activator.ID, NLS.bind(Messages.io_failedRead, toDownload), t); } } diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java index 94bfaf543..381a16e8f 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/RepositoryTransport.java @@ -48,46 +48,91 @@ public class RepositoryTransport extends Transport { /** * Perform a download, writing into the target output stream. Progress is reported on the * monitor. If the <code>target</code> is an instance of {@link IStateful} the resulting status - * is also set on the target. + * is also set on the target. An IStateful target is updated with status even if this methods + * throws {@link OperationCanceledException}. * * @returns IStatus, that is a {@link DownloadStatus} on success. * @param toDownload URI of file to download * @param target OutputStream where result is written + * @param startPos the starting position of the download, or -1 for from start * @param monitor where progress should be reported + * @throws OperationCanceledException if the operation was canceled. */ - public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) { + public IStatus download(URI toDownload, OutputStream target, long startPos, IProgressMonitor monitor) { + boolean promptUser = false; AuthenticationInfo loginDetails = null; for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i++) { + FileReader reader = null; try { loginDetails = Credentials.forLocation(toDownload, promptUser, loginDetails); IConnectContext context = (loginDetails == null) ? null : ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword()); // perform the download - FileReader reader = new FileReader(context); - reader.readInto(toDownload, target, monitor); + reader = new FileReader(context); + reader.readInto(toDownload, target, startPos, monitor); + + // check that job ended ok - throw exceptions otherwise + IStatus result = reader.getResult(); + if (result.getSeverity() == IStatus.CANCEL) + throw new UserCancelledException(); + if (!result.isOK()) + throw new CoreException(result); + // Download status is expected on success DownloadStatus status = new DownloadStatus(IStatus.OK, Activator.ID, Status.OK_STATUS.getMessage()); - status.setTransferRate(reader.getLastFileInfo().getAverageSpeed()); - return statusOn(target, status); + return statusOn(target, status, reader); } catch (UserCancelledException e) { - statusOn(target, Status.CANCEL_STATUS); + statusOn(target, new DownloadStatus(IStatus.CANCEL, Activator.ID, 1, "", null), reader); //$NON-NLS-1$ throw new OperationCanceledException(); + } catch (OperationCanceledException e) { + statusOn(target, new DownloadStatus(IStatus.CANCEL, Activator.ID, 1, "", null), reader); //$NON-NLS-1$ + throw e; } catch (CoreException e) { - return statusOn(target, RepositoryStatus.forStatus(e.getStatus(), toDownload)); + return statusOn(target, RepositoryStatus.forStatus(e.getStatus(), toDownload), reader); } catch (FileNotFoundException e) { - return statusOn(target, RepositoryStatus.forException(e, toDownload)); + return statusOn(target, RepositoryStatus.forException(e, toDownload), reader); } catch (AuthenticationFailedException e) { promptUser = true; } } // reached maximum number of retries without success - IStatus status = new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, // + DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, // NLS.bind(Messages.UnableToRead_0_TooManyAttempts, toDownload), null); - return statusOn(target, status); + return statusOn(target, status, null); + } + + /** + * Perform a download, writing into the target output stream. Progress is reported on the + * monitor. If the <code>target</code> is an instance of {@link IStateful} the resulting status + * is also set on the target. + * + * @returns IStatus, that is a {@link DownloadStatus} on success. + * @param toDownload URI of file to download + * @param target OutputStream where result is written + * @param monitor where progress should be reported + */ + public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) { + return download(toDownload, target, -1, monitor); } - private static IStatus statusOn(OutputStream target, IStatus status) { + /** + * Set the status on the output stream if it implements IStateful. + * Update the DownloadStatus with information from FileReader. + * @param target an OutputStream possibly implementing IStateful + * @param status a DownloadStatus configured with status message, code, etc + * @param reader a FileReade that was used to download (or null if not known). + * @return the configured DownloadStatus status. + */ + private static DownloadStatus statusOn(OutputStream target, DownloadStatus status, FileReader reader) { + if (reader != null) { + FileInfo fi = reader.getLastFileInfo(); + if (fi != null) { + status.setFileSize(fi.getSize()); + status.setLastModified(fi.getLastModified()); + status.setTransferRate(fi.getAverageSpeed()); + } + } if (target instanceof IStateful) ((IStateful) target).setStatus(status); return status; diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/messages.properties b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/messages.properties index 76e3f0934..97e433f1a 100644 --- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/messages.properties +++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/messages.properties @@ -65,7 +65,9 @@ TransportErrorTranslator_UnableToConnectToRepository_0=Unable to connect to repo TransportErrorTranslator_UnknownErrorCode=HTTP Server Unknown HTTP Response Code ({0}):{1} TransportErrorTranslator_UnknownHost=Unknown Host: {0} fetching_0_1_at_2=Fetching {0} ({1} at {2}/s) +fetching_0_from_1_2_at_3=Fetching {0} ({2} at {3}/s) from {1} fetching_0_1_of_2_at_3=Fetching {0} ({1} of {2} at {3}/s) +fetching_0_from_1_2_of_3_at_4=Fetching {0} ({2} of {3} at {4}/s) from {1} FileTransport_reader=File Transport Reader connection_to_0_failed_on_1_retry_attempt_2=Connection to {0} failed on {1}. Retry attempt {2} started UnableToRead_0_TooManyAttempts=Unable to read repository at: {0}. Too many failed attempts. |