| /******************************************************************************* |
| * Copyright (c) 2008, 2011 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Cloudsmith Inc - additional implementation |
| * Sonatype, Inc. - additional implementation and p2 discovery support |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.p2.discovery.compatibility.util; |
| |
| import java.io.*; |
| import java.net.URI; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.equinox.internal.p2.core.helpers.LogHelper; |
| import org.eclipse.equinox.internal.p2.discovery.compatibility.Activator; |
| import org.eclipse.equinox.internal.p2.discovery.compatibility.Messages; |
| import org.eclipse.equinox.internal.p2.repository.AuthenticationFailedException; |
| import org.eclipse.equinox.internal.p2.repository.Transport; |
| import org.eclipse.equinox.internal.provisional.p2.repository.IStateful; |
| import org.eclipse.equinox.p2.core.ProvisionException; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * A class to manage discovery cache files. Creating the cache files will place |
| * the file in the plugin state location in a cache directory. |
| */ |
| @SuppressWarnings("restriction") |
| public class CacheManager { |
| |
| private static final String PREFIX = "discovery"; //$NON-NLS-1$ |
| |
| private static final String DOWNLOADING = "downloading"; //$NON-NLS-1$ |
| |
| private final Transport transport; |
| |
| /** |
| * 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; |
| } |
| |
| } |
| |
| public CacheManager(Transport transport) { |
| this.transport = transport; |
| } |
| |
| /** |
| * Returns a hash of the location. |
| */ |
| private int computeHash(URI location) { |
| return location.hashCode(); |
| } |
| |
| /** |
| * Returns a local cache file with the contents of the given remote location, |
| * or <code>null</code> if a local cache could not be created. |
| * |
| * @param location the remote location to be cached |
| * @param monitor a progress monitor |
| * @return A {@link File} object pointing to the cache file or <code>null</code> |
| * @throws FileNotFoundException if neither jar nor xml index file exists at given location |
| * @throws AuthenticationFailedException if jar not available and xml causes authentication fail |
| * @throws IOException on general IO errors |
| * @throws ProvisionException on any error (e.g. user cancellation, unknown host, malformed address, connection refused, etc.) |
| * @throws OperationCanceledException - if user cancelled |
| */ |
| public File createCache(URI location, IProgressMonitor monitor) throws IOException, ProvisionException { |
| |
| SubMonitor submonitor = SubMonitor.convert(monitor, 1000); |
| try { |
| File cacheFile = getCache(location); |
| |
| boolean stale = true; |
| long lastModified = 0L; |
| |
| if (cacheFile != null) { |
| lastModified = cacheFile.lastModified(); |
| } |
| // get last modified on jar |
| long lastModifiedRemote = 0L; |
| // bug 269588 - server may return 0 when file exists, so extra flag is needed |
| try { |
| lastModifiedRemote = transport.getLastModified(location, submonitor.newChild(1)); |
| if (lastModifiedRemote <= 0) |
| LogHelper.log(new Status(IStatus.WARNING, Activator.ID, "Server returned lastModified <= 0 for " + location)); //$NON-NLS-1$ |
| } catch (AuthenticationFailedException e) { |
| // it is not meaningful to continue - the credentials are for the server |
| // do not pass the exception - it gives no additional meaningful user information |
| throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, NLS.bind(Messages.CacheManager_AuthenticationFaileFor_0, location), null)); |
| } catch (CoreException e) { |
| throw new ProvisionException(e.getStatus()); |
| } catch (OperationCanceledException e) { |
| // must pass this on |
| throw e; |
| } |
| if (submonitor.isCanceled()) |
| throw new OperationCanceledException(); |
| stale = lastModifiedRemote > lastModified || lastModifiedRemote <= 0; |
| |
| if (!stale) |
| return cacheFile; |
| |
| // The cache is stale or missing, so we need to update it from the remote location |
| cacheFile = getCacheFile(location); |
| updateCache(cacheFile, location, lastModifiedRemote, submonitor); |
| return cacheFile; |
| } finally { |
| submonitor.done(); |
| } |
| } |
| |
| /** |
| * Deletes the local cache file(s) for the given location |
| * @param location |
| */ |
| void deleteCache(URI location) { |
| File cacheFile = getCache(location); |
| // delete the cache file if it exists |
| safeDelete(cacheFile); |
| // delete a resumable download if it exists |
| safeDelete(new File(new File(cacheFile.getParentFile(), DOWNLOADING), cacheFile.getName())); |
| } |
| |
| /** |
| * Determines the local file paths of the locations potential cache file. |
| * @param location The location to compute the cache for |
| * @return A {@link File} array with the cache files for JAR and XML extensions. |
| */ |
| private File getCache(URI location) { |
| File cacheFile = getCacheFile(location); |
| return cacheFile.exists() ? cacheFile : null; |
| } |
| |
| private File getCacheFile(URI location) { |
| return new File(getCacheDirectory(), PREFIX + computeHash(location)); |
| } |
| |
| /** |
| * Returns the file corresponding to the data area to be used by the cache manager. |
| */ |
| protected File getCacheDirectory() { |
| return Activator.getDefault().getStateLocation().append("cache").toFile(); //$NON-NLS-1$ |
| } |
| |
| private boolean safeDelete(File file) { |
| if (file.exists()) { |
| if (!file.delete()) { |
| file.deleteOnExit(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected void updateCache(File cacheFile, URI remoteFile, long lastModifiedRemote, SubMonitor submonitor) throws FileNotFoundException, IOException, ProvisionException { |
| cacheFile.getParentFile().mkdirs(); |
| File downloadDir = new File(cacheFile.getParentFile(), DOWNLOADING); |
| if (!downloadDir.exists()) |
| downloadDir.mkdir(); |
| File tempFile = new File(downloadDir, cacheFile.getName()); |
| // Ensure that the file from a previous download attempt is removed |
| if (tempFile.exists()) |
| safeDelete(tempFile); |
| |
| tempFile.createNewFile(); |
| |
| StatefulStream stream = null; |
| try { |
| stream = new StatefulStream(new FileOutputStream(tempFile)); |
| } catch (Exception e) { |
| throw new ProvisionException(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e)); |
| } |
| IStatus result = null; |
| try { |
| submonitor.setWorkRemaining(1000); |
| result = transport.download(remoteFile, stream, 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 = stream.getStatus(); |
| } finally { |
| stream.close(); |
| // If there was any problem fetching the file, delete the temp file |
| if (result == null || !result.isOK()) |
| safeDelete(tempFile); |
| } |
| if (result.isOK()) { |
| if (cacheFile.exists()) |
| safeDelete(cacheFile); |
| if (tempFile.renameTo(cacheFile)) |
| return; |
| result = new Status(IStatus.ERROR, Activator.ID, NLS.bind(Messages.CacheManage_ErrorRenamingCache, new Object[] {remoteFile.toString(), tempFile.getAbsolutePath(), cacheFile.getAbsolutePath()})); |
| } |
| |
| if (result.getSeverity() == IStatus.CANCEL || submonitor.isCanceled()) |
| throw new OperationCanceledException(); |
| throw new ProvisionException(result); |
| } |
| } |