| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.update.core; |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.*; |
| |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.update.core.model.*; |
| import org.eclipse.update.internal.core.*; |
| |
| /** |
| * Base implementation of a feature content provider. This class provides a set |
| * of helper methods useful for implementing feature content providers. In |
| * particular, methods dealing with downloading and caching of feature files. |
| * <p> |
| * This class must be subclassed by clients. |
| * </p> |
| * <p> |
| * <b>Note:</b> This class/interface is part of an interim API that is still under development and expected to |
| * change significantly before reaching stability. It is being made available at this early stage to solicit feedback |
| * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken |
| * (repeatedly) as the API evolves. |
| * </p> |
| * @see org.eclipse.update.core.IFeatureContentProvider |
| * @since 2.0 |
| */ |
| public abstract class FeatureContentProvider |
| implements IFeatureContentProvider { |
| |
| private static final boolean SWITCH_COPY_LOCAL = true; |
| |
| /** |
| * |
| */ |
| public class FileFilter { |
| |
| private IPath filterPath = null; |
| |
| /** |
| * Constructor for FileFilter. |
| */ |
| public FileFilter(String filter) { |
| super(); |
| this.filterPath = new Path(filter); |
| } |
| |
| /** |
| * returns true if the name matches the rule |
| */ |
| public boolean accept(String name) { |
| |
| if (name == null) |
| return false; |
| |
| // no '*' pattern matching |
| // must be equals |
| IPath namePath = new Path(name); |
| if (filterPath.lastSegment().indexOf('*') == -1) { |
| return filterPath.equals(namePath); |
| } |
| |
| // check same file extension if extension exists (a.txt/*.txt) |
| // or same file name (a.txt,a.*) |
| String extension = filterPath.getFileExtension(); |
| if (!extension.equals("*")) { //$NON-NLS-1$ |
| if (!extension.equalsIgnoreCase(namePath.getFileExtension())) |
| return false; |
| } else { |
| IPath noExtension = filterPath.removeFileExtension(); |
| String fileName = noExtension.lastSegment(); |
| if (!fileName.equals("*")) { //$NON-NLS-1$ |
| if (!namePath.lastSegment().startsWith(fileName)) |
| return false; |
| } |
| } |
| |
| // check same path |
| IPath p1 = namePath.removeLastSegments(1); |
| IPath p2 = filterPath.removeLastSegments(1); |
| return p1.equals(p2); |
| } |
| |
| } |
| |
| private URL base; |
| private IFeature feature; |
| private File tmpDir; // local work area for each provider |
| public static final String JAR_EXTENSION = ".jar"; //$NON-NLS-1$ |
| |
| private static final String DOT_PERMISSIONS = "permissions.properties"; //$NON-NLS-1$ |
| private static final String EXECUTABLES = "permissions.executable"; //$NON-NLS-1$ |
| |
| // lock |
| private final static Object lock = new Object(); |
| |
| // hashtable of locks |
| private static Hashtable locks = new Hashtable(); |
| |
| /** |
| * Feature content provider constructor |
| * |
| * @param base |
| * feature URL. The interpretation of this URL is specific to |
| * each content provider. |
| * @since 2.0 |
| */ |
| public FeatureContentProvider(URL base) { |
| this.base = base; |
| this.feature = null; |
| } |
| |
| /** |
| * Returns the feature url. |
| * |
| * @see IFeatureContentProvider#getURL() |
| */ |
| public URL getURL() { |
| return base; |
| } |
| |
| /** |
| * Returns the feature associated with this content provider. |
| * |
| * @see IFeatureContentProvider#getFeature() |
| */ |
| public IFeature getFeature() { |
| return feature; |
| } |
| |
| /** |
| * Sets the feature associated with this content provider. |
| * |
| * @see IFeatureContentProvider#setFeature(IFeature) |
| */ |
| public void setFeature(IFeature feature) { |
| this.feature = feature; |
| } |
| |
| /** |
| * Returns the specified reference as a local file system reference. If |
| * required, the file represented by the specified content reference is |
| * first downloaded to the local system |
| * |
| * @param ref |
| * content reference |
| * @param monitor |
| * progress monitor, can be <code>null</code> |
| * @exception IOException |
| * @exception CoreException |
| * @since 2.0 |
| */ |
| public ContentReference asLocalReference( |
| ContentReference ref, |
| InstallMonitor monitor) |
| throws IOException, CoreException { |
| |
| // check to see if this is already a local reference |
| if (ref.isLocalReference()) |
| return ref; |
| |
| // check to see if we already have a local file for this reference |
| String key = ref.toString(); |
| |
| // need to synch as another thread my have created the file but |
| // is still copying into it |
| File localFile = null; |
| FileFragment localFileFragment = null; |
| Object keyLock = null; |
| synchronized (lock) { |
| if (locks.get(key) == null) |
| locks.put(key, key); |
| keyLock = locks.get(key); |
| } |
| |
| synchronized (keyLock) { |
| localFile = Utilities.lookupLocalFile(key); |
| if (localFile != null) { |
| // check if the cached file is still valid (no newer version on |
| // server) |
| if (UpdateManagerUtils |
| .isSameTimestamp(ref.asURL(), localFile.lastModified())) |
| return ref.createContentReference( |
| ref.getIdentifier(), |
| localFile); |
| } |
| |
| if (localFile == null) { |
| localFileFragment = |
| UpdateManagerUtils.lookupLocalFileFragment(key); |
| } |
| // |
| // download the referenced file into local temporary area |
| InputStream is = null; |
| OutputStream os = null; |
| long bytesCopied = 0; |
| long inputLength = 0; |
| boolean success = false; |
| if (monitor != null) { |
| monitor.saveState(); |
| monitor.setTaskName( |
| Policy.bind("FeatureContentProvider.Downloading")); //$NON-NLS-1$ |
| monitor.subTask(ref.getIdentifier() + " "); //$NON-NLS-1$ |
| monitor.setTotalCount(ref.getInputSize()); |
| monitor.showCopyDetails(true); |
| } |
| |
| try { |
| if (localFileFragment != null |
| && "http".equals(ref.asURL().getProtocol())) { //$NON-NLS-1$ |
| localFile = localFileFragment.getFile(); |
| try { |
| // get partial input stream |
| is = |
| ref.getPartialInputStream( |
| localFileFragment.getSize()); |
| inputLength = ref.getInputSize()-localFileFragment.getSize(); |
| // get output stream to append to file fragment |
| os = |
| new BufferedOutputStream( |
| new FileOutputStream(localFile, true)); |
| } catch (IOException e) { |
| try { |
| if (is != null) |
| is.close(); |
| } catch (IOException ioe) { |
| } |
| is = null; |
| os = null; |
| localFileFragment = null; |
| } |
| } |
| if (is == null) { |
| // must download from scratch |
| localFile = |
| Utilities.createLocalFile(getWorkingDirectory(), null); |
| try { |
| is = ref.getInputStream(); |
| inputLength = ref.getInputSize(); |
| } catch (IOException e) { |
| throw Utilities.newCoreException( |
| Policy.bind( |
| "FeatureContentProvider.UnableToRetrieve", //$NON-NLS-1$ |
| new Object[] { ref }), |
| e); |
| } |
| |
| try { |
| os = |
| new BufferedOutputStream( |
| new FileOutputStream(localFile)); |
| } catch (FileNotFoundException e) { |
| throw Utilities.newCoreException( |
| Policy.bind( |
| "FeatureContentProvider.UnableToCreate", //$NON-NLS-1$ |
| new Object[] { localFile }), |
| e); |
| } |
| } |
| |
| Date start = new Date(); |
| if (localFileFragment != null) { |
| bytesCopied = localFileFragment.getSize(); |
| if (monitor != null) { |
| monitor.incrementCount(bytesCopied); |
| } |
| } |
| |
| // Transfer as many bytes as possible from input to output stream |
| long offset = UpdateManagerUtils.copy(is, os, monitor, inputLength); |
| if (offset != -1) { |
| bytesCopied += offset; |
| if (bytesCopied > 0) { |
| // preserve partially downloaded file |
| UpdateManagerUtils.mapLocalFileFragment( |
| key, |
| new FileFragment(localFile, bytesCopied)); |
| } |
| if (monitor.isCanceled()) { |
| String msg = Policy.bind("Feature.InstallationCancelled"); //$NON-NLS-1$ |
| throw new InstallAbortedException(msg, null); |
| } else { |
| throw new FeatureDownloadException( |
| Policy.bind( |
| "FeatureContentProvider.ExceptionDownloading", //$NON-NLS-1$ |
| new Object[] { getURL().toExternalForm()}), |
| new IOException()); |
| } |
| } else { |
| UpdateManagerUtils.unMapLocalFileFragment(key); |
| } |
| |
| Date stop = new Date(); |
| long timeInseconds = (stop.getTime() - start.getTime()) / 1000; |
| // time in milliseconds /1000 = time in seconds |
| InternalSiteManager.downloaded( |
| ref.getInputSize(), |
| (timeInseconds), |
| ref.asURL()); |
| |
| success = true; |
| |
| // file is downloaded succesfully, map it |
| Utilities.mapLocalFile(key, localFile); |
| } catch (ClassCastException e) { |
| throw Utilities.newCoreException( |
| Policy.bind( |
| "FeatureContentProvider.UnableToCreate", //$NON-NLS-1$ |
| new Object[] { localFile }), |
| e); |
| } finally { |
| //Do not close IS if user cancel, |
| //closing IS will read the entire Stream until the end |
| if (success && is != null) |
| try { |
| is.close(); |
| } catch (IOException e) { |
| } |
| if (os != null) |
| try { |
| os.close(); // should flush buffer stream |
| } catch (IOException e) { |
| } |
| |
| if (success || bytesCopied > 0) { |
| // set the timestamp on the temp file to match the remote |
| // timestamp |
| localFile.setLastModified(ref.getLastModified()); |
| } |
| if (monitor != null) |
| monitor.restoreState(); |
| } |
| locks.remove(key); |
| } // end lock |
| ContentReference reference = |
| ref.createContentReference(ref.getIdentifier(), localFile); |
| return reference; |
| } |
| |
| /** |
| * Returns the specified reference as a local file. If required, the file |
| * represented by the specified content reference is first downloaded to |
| * the local system |
| * |
| * @param ref |
| * content reference |
| * @param monitor |
| * progress monitor, can be <code>null</code> |
| * @exception IOException |
| * @exception CoreException |
| * @since 2.0 |
| */ |
| public File asLocalFile(ContentReference ref, InstallMonitor monitor) |
| throws IOException, CoreException { |
| File file = ref.asFile(); |
| if (file != null && !SWITCH_COPY_LOCAL) |
| return file; |
| ContentReference localRef = asLocalReference(ref, monitor); |
| file = localRef.asFile(); |
| return file; |
| } |
| |
| /** |
| * Returns working directory for this content provider |
| * |
| * @return working directory |
| * @exception IOException |
| * @since 2.0 |
| */ |
| protected File getWorkingDirectory() throws IOException { |
| if (tmpDir == null) |
| tmpDir = Utilities.createWorkingDirectory(); |
| return tmpDir; |
| } |
| |
| /** |
| * Returns the total size of all archives required for the specified |
| * plug-in and non-plug-in entries (the "packaging" view). |
| * |
| * @see IFeatureContentProvider#getDownloadSizeFor(IPluginEntry[], |
| * INonPluginEntry[]) |
| */ |
| public long getDownloadSizeFor( |
| IPluginEntry[] pluginEntries, |
| INonPluginEntry[] nonPluginEntries) { |
| long result = 0; |
| |
| // if both are null or empty, return UNKNOWN size |
| if ((pluginEntries == null || pluginEntries.length == 0) |
| && (nonPluginEntries == null || nonPluginEntries.length == 0)) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| |
| // loop on plugin entries |
| long size = 0; |
| if (pluginEntries != null) |
| for (int i = 0; i < pluginEntries.length; i++) { |
| size = ((PluginEntryModel) pluginEntries[i]).getDownloadSize(); |
| if (size == ContentEntryModel.UNKNOWN_SIZE) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| result += size; |
| } |
| |
| // loop on non plugin entries |
| if (nonPluginEntries != null) |
| for (int i = 0; i < nonPluginEntries.length; i++) { |
| size = |
| ((NonPluginEntryModel) nonPluginEntries[i]) |
| .getDownloadSize(); |
| if (size == ContentEntryModel.UNKNOWN_SIZE) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| result += size; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns the total size of all files required for the specified plug-in |
| * and non-plug-in entries (the "logical" view). |
| * |
| * @see IFeatureContentProvider#getInstallSizeFor(IPluginEntry[], |
| * INonPluginEntry[]) |
| */ |
| public long getInstallSizeFor( |
| IPluginEntry[] pluginEntries, |
| INonPluginEntry[] nonPluginEntries) { |
| long result = 0; |
| |
| // if both are null or empty, return UNKNOWN size |
| if ((pluginEntries == null || pluginEntries.length == 0) |
| && (nonPluginEntries == null || nonPluginEntries.length == 0)) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| |
| // loop on plugin entries |
| long size = 0; |
| if (pluginEntries != null) |
| for (int i = 0; i < pluginEntries.length; i++) { |
| size = ((PluginEntryModel) pluginEntries[i]).getInstallSize(); |
| if (size == ContentEntryModel.UNKNOWN_SIZE) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| result += size; |
| } |
| |
| // loop on non plugin entries |
| if (nonPluginEntries != null) |
| for (int i = 0; i < nonPluginEntries.length; i++) { |
| size = |
| ((NonPluginEntryModel) nonPluginEntries[i]) |
| .getInstallSize(); |
| if (size == ContentEntryModel.UNKNOWN_SIZE) { |
| return ContentEntryModel.UNKNOWN_SIZE; |
| } |
| result += size; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Returns the path identifier for a plugin entry. <code>plugins/<pluginId>_<pluginVersion>.jar</code> |
| * |
| * @return the path identifier |
| */ |
| protected String getPathID(IPluginEntry entry) { |
| return Site.DEFAULT_PLUGIN_PATH |
| + entry.getVersionedIdentifier().toString() |
| + JAR_EXTENSION; |
| } |
| |
| /** |
| * Returns the path identifer for a non plugin entry. <code>features/<featureId>_<featureVersion>/<dataId></code> |
| * |
| * @return the path identifier |
| */ |
| protected String getPathID(INonPluginEntry entry) { |
| String nonPluginBaseID = |
| Site.DEFAULT_FEATURE_PATH |
| + feature.getVersionedIdentifier().toString() |
| + "/"; //$NON-NLS-1$ |
| return nonPluginBaseID + entry.getIdentifier(); |
| } |
| |
| /** |
| * Sets the permission of all the ContentReferences Check for the |
| * .permissions contentReference and use it to set the permissions of other |
| * ContentReference |
| */ |
| protected void validatePermissions(ContentReference[] references) { |
| |
| if (references == null || references.length == 0) |
| return; |
| |
| Map permissions = getPermissions(references); |
| if (permissions.isEmpty()) |
| return; |
| |
| for (int i = 0; i < references.length; i++) { |
| ContentReference contentReference = references[i]; |
| String id = contentReference.getIdentifier(); |
| Object value = null; |
| if ((value = matchesOneRule(id, permissions)) != null) { |
| Integer permission = (Integer) value; |
| contentReference.setPermission(permission.intValue()); |
| } |
| } |
| } |
| |
| /** |
| * Returns the value of the matching rule or <code>null</code> if none |
| * found. A rule is matched if the id is equals to a key, or if the id is |
| * resolved by a key. if the id is <code>/path/file.txt</code> it is |
| * resolved by <code>/path/*</code> or <code>/path/*.txt</code> |
| * |
| * @param id |
| * the identifier |
| * @param permissions |
| * list of rules |
| * @return Object the value of the matcing rule or <code>null</code> |
| */ |
| private Object matchesOneRule(String id, Map permissions) { |
| |
| Set keySet = permissions.keySet(); |
| Iterator iter = keySet.iterator(); |
| while (iter.hasNext()) { |
| FileFilter rule = (FileFilter) iter.next(); |
| if (rule.accept(id)) { |
| return permissions.get(rule); |
| } |
| } |
| |
| return null; |
| } |
| |
| /* |
| * returns the permission MAP |
| */ |
| private Map getPermissions(ContentReference[] references) { |
| |
| Map result = new HashMap(); |
| // search for .permissions |
| boolean notfound = true; |
| ContentReference permissionReference = null; |
| for (int i = 0; i < references.length && notfound; i++) { |
| ContentReference contentReference = references[i]; |
| if (DOT_PERMISSIONS.equals(contentReference.getIdentifier())) { |
| notfound = false; |
| permissionReference = contentReference; |
| } |
| } |
| if (notfound) |
| return result; |
| |
| Properties prop = new Properties(); |
| try { |
| prop.load(permissionReference.getInputStream()); |
| } catch (IOException e) { |
| UpdateCore.warn("", e); //$NON-NLS-1$ |
| } |
| |
| String executables = prop.getProperty(EXECUTABLES); |
| if (executables == null) |
| return result; |
| |
| StringTokenizer tokenizer = new StringTokenizer(executables, ","); //$NON-NLS-1$ |
| Integer defaultExecutablePermission = |
| new Integer(ContentReference.DEFAULT_EXECUTABLE_PERMISSION); |
| while (tokenizer.hasMoreTokens()) { |
| FileFilter filter = new FileFilter(tokenizer.nextToken()); |
| result.put(filter, defaultExecutablePermission); |
| } |
| |
| return result; |
| } |
| } |