Skip to content

Commit

Permalink
[TP-Editor] Propose children of composite repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
HannesWell committed Oct 20, 2024
1 parent 52e96a6 commit cc83723
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*******************************************************************************/
package org.eclipse.pde.internal.genericeditor.target.extension.autocomplete.processors;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
Expand Down Expand Up @@ -111,6 +112,11 @@ public ICompletionProposal[] getCompletionProposals() {
}
}

if (ITargetConstants.REPOSITORY_LOCATION_ATTR.equalsIgnoreCase(acKey)) {
List<URI> children = RepositoryCache.fetchChildrenOfRepo(searchTerm);
return toProposals(children.stream().map(URI::toString));
}

return new ICompletionProposal[] {};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.p2.metadata.IVersionedId;
import org.eclipse.equinox.p2.metadata.VersionedId;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.internal.genericeditor.target.extension.p2.Messages;
import org.eclipse.pde.internal.genericeditor.target.extension.p2.P2Fetcher;
import org.eclipse.pde.internal.genericeditor.target.extension.p2.P2Fetcher.RepositoryContent;

/**
* This class is used to cache the p2 repositories completion information order
Expand All @@ -48,7 +51,10 @@ private RepositoryCache() {
// avoid instantiation
}

private static final Map<URI, CompletableFuture<Map<String, List<IVersionedId>>>> CACHE = new ConcurrentHashMap<>();
private static record RepositoryMetadata(Map<String, List<IVersionedId>> units, List<URI> children) {
}

private static final Map<URI, CompletableFuture<RepositoryMetadata>> CACHE = new ConcurrentHashMap<>();

/**
* Fetches information and caches it.
Expand All @@ -64,36 +70,43 @@ private RepositoryCache() {
*/
public static Map<String, List<IVersionedId>> fetchP2UnitsFromRepos(List<String> repositories) {
if (repositories.size() == 1) {
return getFutureValue(fetchP2DataOfRepo(repositories.get(0)));
return getFutureValue(fetchP2DataOfRepo(repositories.get(0)), RepositoryMetadata::units, Map.of());
}
var repos = repositories.stream().map(RepositoryCache::fetchP2DataOfRepo).toList();
// Fetch all repos at once to await pending metadata in parallel
return toSortedMap(repos.stream().map(RepositoryCache::getFutureValue) //
return toSortedMap(repos.stream()
.map(r -> getFutureValue(r, RepositoryMetadata::units, Map.<String, List<IVersionedId>>of()))
.map(Map::values).flatMap(Collection::stream).flatMap(List::stream));
}

public static List<URI> fetchChildrenOfRepo(String repository) {
return getFutureValue(fetchP2DataOfRepo(repository), RepositoryMetadata::children, List.of());
}

public static void prefetchP2MetadataOfRepository(String repository) {
fetchP2DataOfRepo(repository);
}

private static Future<Map<String, List<IVersionedId>>> fetchP2DataOfRepo(String repository) {
private static Future<RepositoryMetadata> fetchP2DataOfRepo(String repository) {
URI location;
try {
location = new URI(repository);
try { // always have a trailing slash to avoid duplicated cache entries
location = new URI(repository + (repository.endsWith("/") ? "" : "/"));
} catch (URISyntaxException e) {
return CompletableFuture.failedFuture(e);
}
return CACHE.compute(location, (repo, f) -> {
if (f != null && (!f.isDone() || !f.isCompletedExceptionally() && !f.isCancelled())) {
return f; // computation is running or has succeeded
}
CompletableFuture<Map<String, List<IVersionedId>>> future = new CompletableFuture<>();
CompletableFuture<RepositoryMetadata> future = new CompletableFuture<>();
// Fetching P2 repository information is a costly operation
// time-wise. Thus it is done in a job.
Job job = Job.create(NLS.bind(Messages.UpdateJob_P2DataFetch, repo), m -> {
try {
Map<String, List<IVersionedId>> units = toSortedMap(P2Fetcher.fetchAvailableUnits(repo, m));
future.complete(units);
RepositoryContent content = P2Fetcher.fetchAvailableUnits(repo, m);
Map<String, List<IVersionedId>> units = toSortedMap(
content.units().stream().map(iu -> new VersionedId(iu.getId(), iu.getVersion())));
future.complete(new RepositoryMetadata(units, content.children()));
} catch (Throwable e) {
future.completeExceptionally(e);
// Only log the failure, don't open an error-dialog.
Expand All @@ -115,11 +128,12 @@ private static Map<String, List<IVersionedId>> toSortedMap(Stream<IVersionedId>
Collectors.groupingBy(IVersionedId::getId, LinkedHashMap::new, Collectors.toUnmodifiableList()));
}

private static Map<String, List<IVersionedId>> getFutureValue(Future<Map<String, List<IVersionedId>>> future) {
private static <T> T getFutureValue(Future<RepositoryMetadata> future, Function<RepositoryMetadata, T> getter,
T defaultValue) {
try {
return future.get();
return getter.apply(future.get());
} catch (Exception e) { // interrupted, canceled or execution failure
return Map.of();
return defaultValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
package org.eclipse.pde.internal.genericeditor.target.extension.p2;

import java.net.URI;
import java.util.List;
import java.util.stream.Stream;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.IProvisioningAgentProvider;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IVersionedId;
import org.eclipse.equinox.p2.metadata.VersionedId;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.ICompositeRepository;
import org.eclipse.equinox.p2.repository.IRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.eclipse.pde.internal.genericeditor.target.extension.model.UnitNode;
Expand All @@ -39,14 +41,17 @@
*/
public class P2Fetcher {

public static record RepositoryContent(IQueryResult<IInstallableUnit> units, List<URI> children) {
}

/**
* This methods goes 'online' to make contact with a p2 repo and query it.
*
* @param repositoryLocation
* URL string of a p2 repository
* URI pointing to the location of a p2 repository
* @return List of available installable unit models. See {@link UnitNode}
*/
public static Stream<IVersionedId> fetchAvailableUnits(URI repositoryLocation, IProgressMonitor monitor)
public static RepositoryContent fetchAvailableUnits(URI repositoryLocation, IProgressMonitor monitor)
throws CoreException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 31);
BundleContext context = FrameworkUtil.getBundle(P2Fetcher.class).getBundleContext();
Expand All @@ -57,10 +62,23 @@ public static Stream<IVersionedId> fetchAvailableUnits(URI repositoryLocation, I
IMetadataRepositoryManager manager = agent.getService(IMetadataRepositoryManager.class);
IMetadataRepository repository = manager.loadRepository(repositoryLocation, subMonitor.split(30));
IQueryResult<IInstallableUnit> allUnits = repository.query(QueryUtil.ALL_UNITS, subMonitor.split(1));
return allUnits.stream().map(iu -> new VersionedId(iu.getId(), iu.getVersion()));
List<URI> children = allChildren(repository, manager).toList();
return new RepositoryContent(allUnits, children);
} finally {
context.ungetService(sr);
}
}

private static Stream<URI> allChildren(IRepository<?> repository, IMetadataRepositoryManager manager) {
if (repository instanceof ICompositeRepository<?> composite) {
return composite.getChildren().stream().flatMap(uri -> {
try { // repository should already been cached
return Stream.concat(Stream.of(uri), allChildren(manager.loadRepository(uri, null), manager));
} catch (ProvisionException e) {
return Stream.of(uri);
}
});
}
return Stream.empty();
}
}

0 comments on commit cc83723

Please sign in to comment.