blob: 9e7eeb4b5a8e16a243a86e7d67413eb8fe8f8e84 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.internal.mpc.ui.operations;
import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
public class RepositoryTransactionHelper implements AutoCloseable {
private final RepositoryTracker metadataRepositoryTracker;
private final RepositoryTracker artifactRepositoryTracker;
public RepositoryTransactionHelper(IMetadataRepositoryManager metadataRepositoryManager,
IArtifactRepositoryManager artifactRepositoryManager) {
metadataRepositoryTracker = metadataRepositoryManager == null ? null
: new RepositoryTracker(metadataRepositoryManager);
artifactRepositoryTracker = artifactRepositoryManager == null ? null
: new RepositoryTracker(artifactRepositoryManager);
}
public void init() {
if (metadataRepositoryTracker != null) {
metadataRepositoryTracker.init();
}
if (artifactRepositoryTracker != null) {
artifactRepositoryTracker.init();
}
}
public void addRepository(URI uri) {
//add artifact repo first, because if anything goes wrong it's better
//to have artifact without meta than the other way around
if (artifactRepositoryTracker != null) {
artifactRepositoryTracker.addRepository(uri);
}
if (metadataRepositoryTracker != null) {
metadataRepositoryTracker.addRepository(uri);
}
}
public void resetRepository(URI uri) {
if (metadataRepositoryTracker != null) {
metadataRepositoryTracker.resetRepository(uri);
}
if (artifactRepositoryTracker != null) {
artifactRepositoryTracker.resetRepository(uri);
}
}
public void resetAll() {
if (metadataRepositoryTracker != null) {
metadataRepositoryTracker.resetAll();
}
if (artifactRepositoryTracker != null) {
artifactRepositoryTracker.resetAll();
}
}
@Override
public void close() {
resetAll();
}
private enum CleanupAction {
REMOVE, DISABLE;
}
private static class RepositoryTracker implements AutoCloseable {
private static final int ENABLED_STATE_FLAGS = IRepositoryManager.REPOSITORIES_NON_LOCAL
| IRepositoryManager.REPOSITORIES_NON_SYSTEM;
private static final int DISABLED_STATE_FLAGS = IRepositoryManager.REPOSITORIES_DISABLED | ENABLED_STATE_FLAGS;
final IRepositoryManager<?> manager;
final Map<URI, CleanupAction> cleanupActions;
final Map<URI, Integer> initialState;
RepositoryTracker(IRepositoryManager<?> manager) {
this.manager = manager;
this.initialState = new HashMap<>();
this.cleanupActions = new LinkedHashMap<>();
init();
}
public void init() {
if (!cleanupActions.isEmpty()) {
throw new IllegalStateException();
}
initialState.clear();
/* TODO
*
* A lot of side effects are happening when repos are loaded - especially composites:
* - child repos get added, sometimes as system=true/enabled=false, or sometimes as system=false/enabled=false
* - repos might get enabled
* - repos might get converted from system=true to system=false, also changing the type from
* LocalMetadataRepository/simpleRepository to something else
*
* All of this makes it rather hopeless to try and revert the repo state completely...
* (For example, at the time of this writing, loading http://download.eclipse.org/mylyn/releases/latest
* also creates http://download.eclipse.org/mylyn/releases/3.25 and leaves it behind as system=false/enabled=false,
* adding it as a disabled repo to the visible repo list. It is hard to gauge if having those around in meta but not in
* artifact would be safe - see bug 560062 - so it's probably best to always fully load both repos)
*
* That's why we only track some of the known repos and only revert what we explicitly did.
*
* We could possibly add a listener for RepositoryEvent.ADDED and relatives, but we might get false positives if
* user does other repo things in parallel...
*/
URI[] enabledRemoteRepos = manager.getKnownRepositories(ENABLED_STATE_FLAGS);
URI[] disabledRemoteRepos = manager.getKnownRepositories(DISABLED_STATE_FLAGS);
for (URI uri : enabledRemoteRepos) {
initialState.put(uri, ENABLED_STATE_FLAGS);
}
for (URI uri : disabledRemoteRepos) {
initialState.put(uri, DISABLED_STATE_FLAGS);
}
}
public void addRepository(URI uri) {
manager.addRepository(uri);
mergeAction(uri, getCleanupAction(uri));
}
private void mergeAction(URI uri, CleanupAction cleanupAction) {
if (cleanupAction != null) {
cleanupActions.put(uri, cleanupAction);
}
}
private CleanupAction getCleanupAction(URI uri) {
Integer state = initialState.get(uri);
if (state == null) {
//did not exist initially
return CleanupAction.REMOVE;
}
if ((state & IRepositoryManager.REPOSITORIES_DISABLED) != 0) {
//initially disabled
return CleanupAction.DISABLE;
}
return null;
}
public void resetRepository(URI uri) {
CleanupAction action = cleanupActions.remove(uri);
doResetRepository(uri, action);
}
private void doResetRepository(URI uri, CleanupAction action) {
if (action == null) {
return;
}
switch (action) {
case REMOVE:
manager.removeRepository(uri);
break;
case DISABLE:
manager.setEnabled(uri, false);
break;
default:
throw new UnsupportedOperationException(action.name());
}
}
public void resetAll() {
for (Entry<URI, CleanupAction> entry : cleanupActions.entrySet()) {
doResetRepository(entry.getKey(), entry.getValue());
}
cleanupActions.clear();
}
@Override
public void close() {
resetAll();
}
}
}