Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 56e30eef0b50feef690a5fa10c7bf8dba72ab793 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*******************************************************************************
 * Copyright (c) 2008, 2009 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);
	}
}

Back to the top