blob: 72678305d192ba568ef0f04bc396d78b206c2a7d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 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
*******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata.repository;
import java.io.*;
import java.net.URL;
import java.util.EventObject;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.core.helpers.*;
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.core.repository.IRepository;
import org.eclipse.equinox.internal.provisional.p2.core.repository.RepositoryEvent;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* A class to manage metadata cache files. Creating the cache files will place
* the file in the AgentData location in a cache directory.
*
* Using the bus listeners will allow the manager to listen for repository
* events. When a repository is removed, it will remove the cache file if one
* was created for the repository.
*/
public class CacheManager {
private static SynchronousProvisioningListener busListener;
private static final String CONTENT_FILENAME = "content"; //$NON-NLS-1$
private static final String JAR_EXTENSION = ".jar"; //$NON-NLS-1$
private static final String XML_EXTENSION = ".xml"; //$NON-NLS-1$
/**
* Returns a hash of the URL.
*/
private int computeHash(URL repositoryLocation) {
//don't use URL#hashCode because it performs DNS lookups
return repositoryLocation.toExternalForm().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 repositoryLocation
* @param monitor - a progress monitor
* @return A {@link File} object pointing to the cache file or <code>null</code>
* if the location is not a repository.
* @throws IOException
* @throws ProvisionException
*/
public File createCache(URL repositoryLocation, IProgressMonitor monitor) throws IOException, ProvisionException {
File cacheFile = getCache(repositoryLocation);
URL jarLocation = URLMetadataRepository.getActualLocation(repositoryLocation, JAR_EXTENSION);
URL xmlLocation = URLMetadataRepository.getActualLocation(repositoryLocation, XML_EXTENSION);
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);
if (cacheFile == null || isCacheStale(repositoryLocation, cacheFile)) {
long lastModifiedRemote = getTransport().getLastModified(jarLocation);
URL remoteFile;
if (lastModifiedRemote != 0) {
cacheFile = new File(dataAreaFile, CONTENT_FILENAME + hashCode + JAR_EXTENSION);
remoteFile = jarLocation;
} else {
lastModifiedRemote = getTransport().getLastModified(xmlLocation);
if (lastModifiedRemote == 0)
// no jar or xml file found
return null;
cacheFile = new File(dataAreaFile, CONTENT_FILENAME + hashCode + XML_EXTENSION);
remoteFile = xmlLocation;
}
cacheFile.getParentFile().mkdirs();
OutputStream metadata = new BufferedOutputStream(new FileOutputStream(cacheFile));
try {
IStatus result = getTransport().download(remoteFile.toExternalForm(), metadata, monitor);
if (!result.isOK()) {
throw new ProvisionException(result);
}
} finally {
metadata.close();
}
}
return cacheFile;
}
/**
* Deletes the local cache file for the given repository
* @param repositoryLocation
*/
void deleteCache(URL repositoryLocation) {
File cacheFile = getCache(repositoryLocation);
if (cacheFile != null)
safeDelete(cacheFile);
}
/**
* Determines the local filepath of the repository's cache file.
* @param repositoryLocation
* @return A {@link File} pointing to the cache file or <code>null</code> if
* the cache file does not exist.
*/
private File getCache(URL repositoryLocation) {
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, CONTENT_FILENAME + hashCode + JAR_EXTENSION);
if (!cacheFile.exists()) {
cacheFile = new File(dataAreaFile, CONTENT_FILENAME + hashCode + XML_EXTENSION);
if (!cacheFile.exists())
return null;
}
return cacheFile;
}
private Object getService(BundleContext ctx, String name) {
ServiceReference reference = ctx.getServiceReference(name);
if (reference == null)
return null;
Object result = ctx.getService(reference);
ctx.ungetService(reference);
return result;
}
private ECFMetadataTransport getTransport() {
return ECFMetadataTransport.getInstance();
}
/**
* Checks if the repository's local cache file is out of date.
* @param repositoryLocation The remote location of the file
* @param cacheFile The current local cache of the remote location
* @return <code>true</code> if the cache file is out of date, <code>false</code>
* if the cache file is in sync with the repository. The cache file is
* considered stale if there is no local cache file.
*/
private boolean isCacheStale(URL repositoryLocation, File cacheFile) {
long lastModified = cacheFile.lastModified();
String name = cacheFile.getName();
URL metadataLocation = null;
if (name.endsWith(XML_EXTENSION)) {
metadataLocation = URLMetadataRepository.getActualLocation(repositoryLocation, XML_EXTENSION);
} else if (name.endsWith(JAR_EXTENSION)) {
metadataLocation = URLMetadataRepository.getActualLocation(repositoryLocation, JAR_EXTENSION);
}
long lastModifiedRemote = 0;
try {
lastModifiedRemote = getTransport().getLastModified(metadataLocation);
} catch (ProvisionException e) {
// cache is stale
return true;
}
return lastModifiedRemote > lastModified ? true : false;
}
/**
* Adds a {@link SynchronousProvisioningListener} to the event bus for
* deleting cache files when the corresponding repository is deleted.
*/
public void registerRepoEventListener() {
IProvisioningEventBus eventBus = (IProvisioningEventBus) getService(Activator.getContext(), IProvisioningEventBus.SERVICE_NAME);
if (eventBus == null) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "ProvisioningEventBus could not be obtained. Metadata caches may not be cleaned up properly.")); //$NON-NLS-1$
return;
}
if (busListener == null) {
busListener = new SynchronousProvisioningListener() {
public void notify(EventObject o) {
if (o instanceof RepositoryEvent) {
RepositoryEvent event = (RepositoryEvent) o;
if (RepositoryEvent.REMOVED == event.getKind() && IRepository.TYPE_METADATA == event.getRepositoryType()) {
deleteCache(event.getRepositoryLocation());
}
}
}
};
eventBus.addListener(busListener);
}
}
private boolean safeDelete(File file) {
if (file.exists()) {
if (!file.delete()) {
file.deleteOnExit();
return true;
}
}
return false;
}
/**
* Removes the {@link SynchronousProvisioningListener} that cleans up the
* cache file from the event bus.
*/
public void unregisterRepoEventListener() {
IProvisioningEventBus eventBus = (IProvisioningEventBus) getService(Activator.getContext(), IProvisioningEventBus.SERVICE_NAME);
if (eventBus != null && busListener != null) {
eventBus.removeListener(busListener);
busListener = null;
}
}
}