Skip to main content
summaryrefslogtreecommitdiffstats
blob: 717920796b584b6f414b21d212c91b2a87b152d1 (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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/*******************************************************************************
 * Copyright (c) 2006-2009, IBM Corporation and other.
 * 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.
 * 
 * Contributors
 * 	IBM Corporation - Initial API and implementation.
 *  Cloudsmith Inc - Implementation
 ******************************************************************************/

package org.eclipse.equinox.internal.p2.repository;

import org.eclipse.equinox.p2.core.ProvisionException;

import java.io.*;
import java.net.URI;
import org.eclipse.core.runtime.*;
import org.eclipse.ecf.core.security.ConnectContextFactory;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.filetransfer.UserCancelledException;
import org.eclipse.equinox.internal.p2.repository.Credentials.LoginCanceledException;
import org.eclipse.equinox.internal.provisional.p2.core.IServiceUI.AuthenticationInfo;
import org.eclipse.equinox.internal.provisional.p2.repository.IStateful;
import org.eclipse.osgi.util.NLS;

/**
 * RepositoryTransport adapts p2 to ECF file download and file browsing.
 * Download is performed by {@link FileReader}, and file browsing is performed by
 * {@link FileInfoReader}.
 */
public class RepositoryTransport extends Transport {
	private static RepositoryTransport instance;

	/**
	 * Returns an shared instance of Generic Transport
	 */
	public static synchronized RepositoryTransport getInstance() {
		if (instance == null) {
			instance = new RepositoryTransport();
		}
		return instance;
	}

	/**
	 * 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. 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, long startPos, IProgressMonitor monitor) {

		boolean promptUser = false;
		boolean useJREHttp = 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
				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());
				return statusOn(target, status, reader);
			} catch (UserCancelledException e) {
				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) {
				if (e.getStatus().getException() == null)
					return statusOn(target, RepositoryStatus.forException(e, toDownload), reader);
				return statusOn(target, RepositoryStatus.forStatus(e.getStatus(), toDownload), reader);
			} catch (FileNotFoundException e) {
				return statusOn(target, RepositoryStatus.forException(e, toDownload), reader);
			} catch (AuthenticationFailedException e) {
				promptUser = true;
			} catch (Credentials.LoginCanceledException e) {
				DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, //
						NLS.bind(Messages.UnableToRead_0_UserCanceled, toDownload), null);
				return statusOn(target, status, null);
			} catch (JREHttpClientRequiredException e) {
				if (!useJREHttp) {
					useJREHttp = true; // only do this once
					i++; // need an extra retry
					Activator.getDefault().useJREHttpClient();
				}
			}
		}
		// reached maximum number of retries without success
		DownloadStatus status = new DownloadStatus(IStatus.ERROR, Activator.ID, ProvisionException.REPOSITORY_FAILED_AUTHENTICATION, //
				NLS.bind(Messages.UnableToRead_0_TooManyAttempts, toDownload), null);
		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
	 * @throws OperationCanceledException if the operation was canceled.
	 */
	public IStatus download(URI toDownload, OutputStream target, IProgressMonitor monitor) {
		return download(toDownload, target, -1, monitor);
	}

	/**
	 * Perform a stream download, writing into an InputStream that is returned. Performs authentication if needed.
	 * 
	 * @returns InputStream a stream with the content from the toDownload URI, or null
	 * @param toDownload URI of file to download
	 * @param monitor monitor checked for cancellation
	 * @throws OperationCanceledException if the operation was canceled.
	 * @throws AuthenticationFailedException if authentication failed, or too many attempt were made
	 * @throws FileNotFoundException if the toDownload was reported as non existing
	 * @throws CoreException on errors
	 */
	public InputStream stream(URI toDownload, IProgressMonitor monitor) throws FileNotFoundException, CoreException, AuthenticationFailedException {

		boolean promptUser = false;
		boolean useJREHttp = 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 streamed download
				reader = new FileReader(context);
				return reader.read(toDownload, monitor);
			} catch (UserCancelledException e) {
				throw new OperationCanceledException();
			} catch (AuthenticationFailedException e) {
				promptUser = true;
			} catch (CoreException e) {
				// must translate this core exception as it is most likely not informative to a user
				if (e.getStatus().getException() == null)
					throw new CoreException(RepositoryStatus.forException(e, toDownload));
				throw new CoreException(RepositoryStatus.forStatus(e.getStatus(), toDownload));
			} catch (LoginCanceledException e) {
				// i.e. same behavior when user cancels as when failing n attempts.
				throw new AuthenticationFailedException();
			} catch (JREHttpClientRequiredException e) {
				if (!useJREHttp) {
					useJREHttp = true; // only do this once
					i++; // need an extra retry
					Activator.getDefault().useJREHttpClient();
				}
			}
		}
		throw new AuthenticationFailedException();
	}

	/**
	 * 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).
	 * @throws OperationCanceledException if the operation was canceled by the user.
	 * @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;
	}

	/**
	 * Returns the last modified date for a URI. A last modified of 0 typically indicates that
	 * the server response is wrong, but should not be interpreted as a file not found.
	 * @param toDownload
	 * @param monitor
	 * @throws OperationCanceledException if the operation was canceled by the user.
	 * @return last modified date (possibly 0)
	 */
	public long getLastModified(URI toDownload, IProgressMonitor monitor) throws CoreException, FileNotFoundException, AuthenticationFailedException {
		boolean promptUser = false;
		boolean useJREHttp = false;
		AuthenticationInfo loginDetails = null;
		for (int i = RepositoryPreferences.getLoginRetryCount(); i > 0; i--) {
			try {
				loginDetails = Credentials.forLocation(toDownload, promptUser, loginDetails);
				IConnectContext context = (loginDetails == null) ? null : ConnectContextFactory.createUsernamePasswordConnectContext(loginDetails.getUserName(), loginDetails.getPassword());
				// get the remote info
				FileInfoReader reader = new FileInfoReader(context);
				return reader.getLastModified(toDownload, monitor);
			} catch (UserCancelledException e) {
				throw new OperationCanceledException();
			} catch (CoreException e) {
				// must translate this core exception as it is most likely not informative to a user
				if (e.getStatus().getException() == null)
					throw new CoreException(RepositoryStatus.forException(e, toDownload));
				throw new CoreException(RepositoryStatus.forStatus(e.getStatus(), toDownload));
			} catch (AuthenticationFailedException e) {
				promptUser = true;
			} catch (LoginCanceledException e) {
				// same behavior as if user failed n attempts.
				throw new AuthenticationFailedException();
			} catch (JREHttpClientRequiredException e) {
				if (!useJREHttp) {
					useJREHttp = true; // only do this once
					i++; // need an extra retry
					Activator.getDefault().useJREHttpClient();
				}
			}

		}
		// reached maximum number of authentication retries without success
		throw new AuthenticationFailedException();
	}

}

Back to the top