blob: c5dffad71f04806eef3916763975c8ad1a56a80e [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.core.pkgmanager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.osgi.service.prefs.BackingStoreException;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.util.NLS;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImIdentitySet;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.ecommons.collections.FastList;
import org.eclipse.statet.ecommons.preferences.core.Preference.NullableStringPref;
import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
import org.eclipse.statet.ecommons.preferences.core.PreferenceSetService;
import org.eclipse.statet.ecommons.preferences.core.PreferenceSetService.ChangeEvent;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.internal.r.core.RCorePlugin;
import org.eclipse.statet.internal.r.core.renv.REnvConfigurationImpl;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.pkgmanager.IRPkgData;
import org.eclipse.statet.r.core.pkgmanager.IRPkgInfoAndData;
import org.eclipse.statet.r.core.pkgmanager.IRPkgManager;
import org.eclipse.statet.r.core.pkgmanager.IRPkgSet;
import org.eclipse.statet.r.core.pkgmanager.IRView;
import org.eclipse.statet.r.core.pkgmanager.ISelectedRepos;
import org.eclipse.statet.r.core.pkgmanager.RPkgAction;
import org.eclipse.statet.r.core.pkgmanager.RPkgUtils;
import org.eclipse.statet.r.core.pkgmanager.RRepo;
import org.eclipse.statet.r.core.pkgmanager.RRepoMirror;
import org.eclipse.statet.r.core.pkgmanager.SelectedRepos;
import org.eclipse.statet.r.core.renv.IREnvConfiguration;
import org.eclipse.statet.r.core.tool.AbstractStatetRRunnable;
import org.eclipse.statet.r.core.tool.IRConsoleService;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataFrame;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.RLogicalStore;
import org.eclipse.statet.rj.data.RNumericStore;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RStore;
import org.eclipse.statet.rj.data.RVector;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.data.impl.RCharacter32Store;
import org.eclipse.statet.rj.data.impl.RVectorImpl;
import org.eclipse.statet.rj.renv.core.BasicRPkgCompilation;
import org.eclipse.statet.rj.renv.core.REnv;
import org.eclipse.statet.rj.renv.core.REnvConfiguration;
import org.eclipse.statet.rj.renv.core.RNumVersion;
import org.eclipse.statet.rj.renv.core.RPkg;
import org.eclipse.statet.rj.renv.core.RPkgCompilation;
import org.eclipse.statet.rj.renv.core.RPkgType;
import org.eclipse.statet.rj.renv.runtime.RPkgManager;
import org.eclipse.statet.rj.renv.runtime.RPkgManagerDataset;
import org.eclipse.statet.rj.renv.runtime.RuntimeRLibPaths;
import org.eclipse.statet.rj.renv.runtime.RuntimeRLibPathsLoader;
import org.eclipse.statet.rj.server.util.ServerUtils;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RPlatform;
import org.eclipse.statet.rj.services.RService;
import org.eclipse.statet.rj.ts.core.AbstractRToolRunnable;
import org.eclipse.statet.rj.ts.core.RToolService;
public class RPkgManagerImpl implements IRPkgManager.Ext, PreferenceSetService.ChangeListener {
private static final int REQUIRE_CRAN= 0x0_1000_0000;
private static final int REQUIRE_BIOC= 0x0_2000_0000;
private static final int REQUIRE_REPOS= 0x0_8000_0000;
private static final int REQUIRE_REPO_PKGS= 0x0_0100_0000;
private static final int REQUIRE_INST_PKGS= 0x0_0800_0000;
private static final ImIdentitySet<String> PREF_QUALIFIERS= ImCollections.newIdentitySet(
PREF_QUALIFIER );
private static final RRepoPref LAST_CRAN_PREF= new RRepoPref(PREF_QUALIFIER, "LastCRAN.repo"); //$NON-NLS-1$
private static final RRepoPref LAST_BIOC_PREF= new RRepoPref(PREF_QUALIFIER, "LastBioC.repo"); //$NON-NLS-1$
private static final int MIRROR_CHECK= 1000 * 60 * 60 * 6;
private static final int PKG_CHECK= 1000 * 60 * 60 * 3;
private final REnv rEnv;
private RPlatform rPlatform;
private final PreferenceAccess prefAccess;
private final IFileStore rEnvDirectory;
private boolean firstTime;
private String bioCVersion;
private final NullableStringPref bioCVersionPref;
private List<RRepo> customRepos;
private final List<RRepo> addRepos;
private List<RRepo> rRepos;
private List<RRepo> allRepos;
private List<RRepo> selectedReposInR;
private final RRepoListPref selectedReposPref;
private List<RRepo> customCRAN;
private List<RRepoMirror> rCRANMirrors;
private ImList<RRepo> allCRAN;
private String selectedCRANInR;
private final RRepoPref selectedCRANPref;
private List<RRepo> customBioC;
private List<RRepoMirror> rBioCMirrors;
private ImList<RRepo> allBioC;
private String selectedBioCInR;
private final RRepoPref selectedBioCPref;
private long mirrorsStamp;
private SelectedRepos selectedRepos;
private RVector<RNumericStore> libs= null;
private RuntimeRLibPathsLoader rLibGroups;
private RuntimeRLibPaths rLibPaths;
private RPkgSet dataset;
private FullRPkgSet datasetExt;
private long pkgsStamp;
final RPkgScanner pkgScanner= new RPkgScanner();
private int requested;
private volatile int requireLoad;
private volatile int requireConfirm;
private final FastList<Listener> listeners= new FastList<>(Listener.class);
private final ReentrantReadWriteLock lock= new ReentrantReadWriteLock();
private List<RView> rViews;
private RNumVersion rViewsVersion;
// private List<RView> bioCViews;
// private String bioCViewsVersion;
// private long bioCViewsStamp;
private Tool rProcess;
private int rTask;
private Change rTaskEvent;
private final DB db;
private final Cache cache;
public RPkgManagerImpl(final IREnvConfiguration config) {
this.rEnv= config.getREnv();
this.rEnvDirectory= EFS.getLocalFileSystem().getStore(REnvConfigurationImpl.getStateLocation(this.rEnv));
final String qualifier= config.getPrefNodeQualifier();
this.selectedReposPref= new RRepoListPref(qualifier, "RPkg.Repos.repos"); //$NON-NLS-1$
this.selectedCRANPref= new RRepoPref(qualifier, "RPkg.CRANMirror.repo"); //$NON-NLS-1$
this.bioCVersionPref= new NullableStringPref(qualifier, "RPkg.BioCVersion.ver"); //$NON-NLS-1$
this.selectedBioCPref= new RRepoPref(qualifier, "RPkg.BioCMirror.repo"); //$NON-NLS-1$
this.prefAccess= PreferenceUtils.getInstancePrefs();
this.addRepos= new ArrayList<>();
if (config.getType() == IREnvConfiguration.USER_LOCAL_TYPE) {
final String rjVersion= "" + ServerUtils.RJ_VERSION[0] + '.' + ServerUtils.RJ_VERSION[1]; //$NON-NLS-1$
this.addRepos.add(new RRepo(RRepo.SPECIAL_PREFIX + "rj", "RJ", "http://download.walware.de/rj-" + rjVersion, null)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
this.selectedRepos= new SelectedRepos(
this.prefAccess.getPreferenceValue(this.selectedReposPref),
this.prefAccess.getPreferenceValue(this.selectedCRANPref),
this.prefAccess.getPreferenceValue(this.bioCVersionPref),
this.prefAccess.getPreferenceValue(this.selectedBioCPref) );
this.db= DB.create(this.rEnv, this.rEnvDirectory);
this.cache= new Cache(this.rEnvDirectory);
resetPkgs(config);
this.firstTime= true;
this.mirrorsStamp= this.pkgsStamp= System.currentTimeMillis();
this.requireLoad |= (REQUIRE_CRAN | REQUIRE_BIOC | REQUIRE_REPOS);
this.requireLoad |= (REQUIRE_REPO_PKGS | REQUIRE_INST_PKGS);
this.prefAccess.addPreferenceSetListener(this, PREF_QUALIFIERS);
getWriteLock().lock();
try {
loadPrefs(true);
}
finally {
getWriteLock().unlock();
}
}
private void resetPkgs(final REnvConfiguration rEnvConfig) {
this.datasetExt= null;
if (this.db != null && rEnvConfig != null) {
this.dataset= new RPkgSet(this.rEnv, rEnvConfig,
this.db.loadInstalled(rEnvConfig.getRLibGroups()) );
}
else {
this.dataset= null;
}
}
@Override
public REnv getREnv() {
return this.rEnv;
}
Cache getCache() {
return this.cache;
}
@Override
public RPlatform getRPlatform() {
return this.rPlatform;
}
@Override
public Lock getReadLock() {
return this.lock.readLock();
}
@Override
public Lock getWriteLock() {
return this.lock.writeLock();
}
@Override
public void preferenceChanged(final ChangeEvent event) {
if (event.contains(PREF_QUALIFIER)
&& (event.contains(CUSTOM_REPO_PREF)
|| event.contains(CUSTOM_CRAN_MIRROR_PREF)
|| event.contains(CUSTOM_BIOC_MIRROR_PREF) )) {
loadPrefs(true);
}
}
public void dispose() {
this.prefAccess.removePreferenceSetListener(this);
}
@Override
public void check(final int flags,
final RService r, final ProgressMonitor m) throws StatusException {
checkInit(flags, r, m);
check(r, m);
}
protected int checkRequest(int request) {
request= RPkgManager.expandFlags(request);
request= checkAutoRefresh(request);
return request;
}
private int checkAutoRefresh(int request) {
final long stamp= System.currentTimeMillis();
if ((request & AVAILABLE_REPOS) != 0
&& Math.abs(this.mirrorsStamp - stamp) > MIRROR_CHECK) {
request |= REFRESH_AVAILABLE_REPOS;
}
if ((request & AVAILABLE_PKGS) != 0
&& Math.abs(this.pkgsStamp - stamp) > PKG_CHECK) {
request |= REFRESH_AVAILABLE_PKGS;
}
return request;
}
@Override
public int request(int request) {
request= checkRequest(request);
getWriteLock().lock();
try {
this.requested |= request;
}
finally {
getWriteLock().unlock();
}
final RPkgManagerDataset dataset= getDataset();
if (dataset != null && (dataset.getProviding() & request) == request) {
return OK;
}
if ((request & (AVAILABLE_PKGS | REFRESH_AVAILABLE_REPOS)) == AVAILABLE_PKGS
&& dataset != null && (dataset.getProviding() & AVAILABLE_REPOS) != 0) {
final Status status= getReposStatus(null);
if (status.getSeverity() > Status.INFO) {
return REQUIRES_CONFIG;
}
}
return REQUIRES_UPDATE;
}
private void checkInit(final int flags,
final RService r, final ProgressMonitor m) throws StatusException {
if ((flags & INITIAL) == INITIAL || this.rPlatform == null) {
checkRVersion(r.getPlatform());
final REnvConfiguration rEnvConfig= this.rEnv.get(REnvConfiguration.class);
if (rEnvConfig != null && rEnvConfig.isRemote()) {
this.rLibGroups= REnvLibGroups.loadFromR(r, m);
}
}
}
private void check(
final RService r, final ProgressMonitor m) throws StatusException {
if (!beginRTaskSilent((RToolService) r, m)) {
return;
}
try {
checkInstalled(false, null, r, m);
if (this.rTaskEvent != null) {
fireUpdate(this.rTaskEvent);
}
}
catch (final Exception e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when checking for new and updated R packages.",
e ));
}
finally {
this.rTaskEvent= null;
endRTask();
}
}
private void checkRVersion(final RPlatform rPlatform) {
if (this.rPlatform != null && !this.rPlatform.getRVersion().equals(rPlatform.getRVersion())) {
getWriteLock().lock();
try {
this.requireLoad |= (REQUIRE_REPOS | REQUIRE_CRAN | REQUIRE_BIOC);
this.requireLoad |= (REQUIRE_REPO_PKGS | REQUIRE_INST_PKGS);
}
finally {
getWriteLock().unlock();
}
}
this.rPlatform= rPlatform;
}
private void updateRequireLoad() {
getWriteLock().lock();
try {
final int requested= checkAutoRefresh(this.requested);
if ((requested & RESET) != 0) {
this.selectedRepos= new SelectedRepos(Collections.<RRepo> emptyList(),
null, null, null);
savePrefs(this.selectedRepos);
this.requireLoad |= (REQUIRE_REPOS | REQUIRE_CRAN | REQUIRE_BIOC);
final Change change= new Change(this.rEnv);
change.repos= 1;
checkRepos(change);
this.requireLoad |= (REQUIRE_REPO_PKGS | REQUIRE_INST_PKGS);
final REnvConfiguration rEnvConfig= this.rEnv.get(REnvConfiguration.class);
resetPkgs(rEnvConfig);
this.firstTime= true;
}
else {
if ((requested & REFRESH_INSTALLED_PKGS) != 0) {
this.requireLoad= (REQUIRE_INST_PKGS);
}
if ((requested & REFRESH_AVAILABLE_REPOS) != 0) {
this.requireLoad |= (REQUIRE_CRAN | REQUIRE_BIOC);
}
if ((requested & REFRESH_AVAILABLE_PKGS) != 0) {
this.requireLoad |= (REQUIRE_REPO_PKGS);
}
}
this.requested &= INSTALLED | AVAILABLE;
}
finally {
getWriteLock().unlock();
}
}
@Override
public Status getReposStatus(final ISelectedRepos repos) {
final ISelectedRepos current= this.selectedRepos;
final int confirm= this.requireConfirm;
return getReposStatus((repos != null) ? repos : current, current, confirm);
}
private Status getReposStatus(final ISelectedRepos repos, final ISelectedRepos current, final int confirm) {
if (repos.getRepos().isEmpty()) {
return createStatus(Status.ERROR, "No repository is selected. Select the repositories where to install R packages from.");
}
final boolean requireCRAN= RVarRepo.requireCRANMirror(repos.getRepos());
if (requireCRAN && repos.getCRANMirror() == null) {
return createStatus(Status.ERROR, "No CRAN mirror is selected. Selected a mirror for CRAN.");
}
final boolean requireBioC= RVarRepo.requireBioCMirror(repos.getRepos());
if (requireBioC && repos.getBioCMirror() == null) {
return createStatus(Status.ERROR, "No BioC mirror is selected. Selected a mirror for Bioconductor.");
}
if ((requireCRAN && (confirm & REQUIRE_CRAN) != 0)
|| (requireBioC && (confirm & REQUIRE_BIOC) != 0)
|| (repos != current && !repos.equals(current) )) {
return createStatus(Status.INFO, "Check the repository settings and confirm with 'Apply' to show the available R packages.");
}
return Statuses.OK_STATUS;
}
private static Status createStatus(final int severity, final String message) {
return Statuses.newStatus(severity, RCore.BUNDLE_ID, message);
}
@Override
public void update(final RService r, final ProgressMonitor m) throws StatusException {
beginRTask((RToolService) r, m);
try {
checkInit(0, r, m);
this.rTaskEvent= new Change(this.rEnv);
final ISelectedRepos settings= runLoadRepos(r, m);
if (settings != null) {
runApplyRepo(settings, r, m);
}
runLoadPkgs(settings, r, m);
fireUpdate(this.rTaskEvent);
}
finally {
this.rTaskEvent= null;
endRTask();
}
}
private void checkMirrors(final Change event) {
if ((this.requireLoad & (REQUIRE_CRAN | REQUIRE_BIOC)) != 0) {
return;
}
SelectedRepos selected= this.selectedRepos;
this.allCRAN= ImCollections.concatList(this.customCRAN, this.rCRANMirrors);
RRepo selectedCRAN= selected.getCRANMirror();
if (selected.getCRANMirror() != null) {
selectedCRAN= Util.findRepo(this.allCRAN, selectedCRAN);
}
else if (this.firstTime && this.selectedCRANInR != null) {
selectedCRAN= Util.getRepoByURL(this.allCRAN, this.selectedCRANInR);
}
if (selectedCRAN == null) {
this.requireConfirm |= REQUIRE_CRAN;
selectedCRAN= this.prefAccess.getPreferenceValue(LAST_CRAN_PREF);
if (selectedCRAN != null) {
selectedCRAN= Util.findRepo(this.allCRAN, selectedCRAN);
}
if (!this.customCRAN.isEmpty()
&& (selectedCRAN == null || !selectedCRAN.getId().startsWith(RRepo.CUSTOM_PREFIX)) ) {
selectedCRAN= this.customCRAN.get(0);
}
if (this.firstTime && selectedCRAN == null && !this.rCRANMirrors.isEmpty()) {
selectedCRAN= getRegionMirror(this.rCRANMirrors);
}
}
RRepo selectedBioC= selected.getBioCMirror();
this.allBioC= ImCollections.concatList(this.customBioC, this.rBioCMirrors);
if (selectedBioC != null) {
selectedBioC= Util.findRepo(this.allBioC, selectedBioC);
}
else if (this.firstTime && this.selectedBioCInR != null) {
selectedBioC= RPkgUtils.getRepoByURL(this.allBioC, this.selectedBioCInR);
}
if (selectedBioC == null) {
this.requireConfirm |= REQUIRE_BIOC;
selectedBioC= this.prefAccess.getPreferenceValue(LAST_BIOC_PREF);
if (!this.customBioC.isEmpty()
&& (selectedBioC == null || !selectedBioC.getId().startsWith(RRepo.CUSTOM_PREFIX)) ) {
selectedBioC= this.customBioC.get(0);
}
if (this.firstTime && selectedBioC == null && !this.rBioCMirrors.isEmpty()) {
selectedBioC= getRegionMirror(this.rBioCMirrors);
if (selectedBioC == null) {
selectedBioC= this.rBioCMirrors.get(0);
}
}
}
selected= new SelectedRepos(
selected.getRepos(),
selectedCRAN,
this.bioCVersion,
selectedBioC );
if ((this.requireLoad & (REQUIRE_REPOS)) == 0) {
for (final RRepo repo : this.allRepos) {
if (repo instanceof RVarRepo) {
((RVarRepo) repo).updateURL(selected);
}
}
}
this.selectedRepos= selected;
event.pkgs= 1;
}
private void checkRepos(final Change event) {
if ((this.requireLoad & (REQUIRE_CRAN | REQUIRE_BIOC | REQUIRE_REPOS)) != 0) {
return;
}
SelectedRepos selected= this.selectedRepos;
this.allRepos= new ArrayList<>(this.customRepos.size() + this.addRepos.size() + this.rRepos.size());
this.allRepos.addAll(this.customRepos);
this.allRepos.addAll(this.addRepos);
for (final RRepo repo : this.allRepos) {
if (repo instanceof RVarRepo) {
((RVarRepo) repo).updateURL(selected);
}
}
for (final RRepo repo : this.rRepos) {
if (repo instanceof RVarRepo) {
((RVarRepo) repo).updateURL(selected);
}
}
for (final RRepo repo : this.rRepos) {
if (!repo.getId().isEmpty()) {
if (RPkgUtils.getRepoById(this.allRepos, repo.getId()) == null) {
this.allRepos.add(repo);
}
}
else {
if (Util.getRepoByURL(this.allRepos, repo) == null) {
this.allRepos.add(RVarRepo.create(RRepo.R_PREFIX + repo.getURL(), repo.getName(),
repo.getURL(), null ));
}
}
}
{ final Collection<RRepo> selectedRepos= selected.getRepos();
final Collection<RRepo> previous= (this.firstTime && selectedRepos.isEmpty()) ?
this.selectedReposInR : selectedRepos;
final List<RRepo> repos= new ArrayList<>(previous.size());
for (RRepo repo : previous) {
repo= Util.findRepo(this.allRepos, repo);
if (repo != null) {
repos.add(repo);
}
}
selected= new SelectedRepos(
repos,
selected.getCRANMirror(),
selected.getBioCVersion(),
selected.getBioCMirror() );
this.selectedRepos= selected;
}
this.requireLoad |= REQUIRE_REPO_PKGS;
event.repos= 1;
}
private void loadPrefs(final boolean custom) {
final Change event= new Change(this.rEnv);
final PreferenceAccess prefs= PreferenceUtils.getInstancePrefs();
getWriteLock().lock();
try {
if (custom) {
this.customRepos= prefs.getPreferenceValue(CUSTOM_REPO_PREF);
this.customCRAN= prefs.getPreferenceValue(CUSTOM_CRAN_MIRROR_PREF);
this.customBioC= prefs.getPreferenceValue(CUSTOM_BIOC_MIRROR_PREF);
checkRepos(event);
}
}
finally {
getWriteLock().unlock();
}
fireUpdate(event);
}
@Override
public void addListener(final Listener listener) {
this.listeners.add(listener);
}
@Override
public void removeListener(final Listener listener) {
this.listeners.remove(listener);
}
private void fireUpdate(final Event event) {
// if (event.reposChanged() == 0 && event.pkgsChanged() == 0 && event.viewsChanged() == 0) {
// return;
// }
final Listener[] listeners= this.listeners.toArray();
for (int i= 0; i < listeners.length; i++) {
listeners[i].handleChange(event);
}
}
@Override
public List<RRepo> getAvailableRepos() {
return this.allRepos;
}
@Override
public ISelectedRepos getSelectedRepos() {
return this.selectedRepos;
}
@Override
public void setSelectedRepos(final ISelectedRepos repos) {
List<RRepo> selectedRepos;
{ final Collection<RRepo> selected= repos.getRepos();
selectedRepos= new ArrayList<>(selected.size());
for (final RRepo repo : this.allRepos) {
if (selected.contains(repo)) {
selectedRepos.add(repo);
}
}
}
RRepo selectedCRAN;
{ final RRepo repo= repos.getCRANMirror();
selectedCRAN= (repo != null) ? Util.findRepo(this.allCRAN, repo) : null;
this.requireConfirm &= ~REQUIRE_CRAN;
}
RRepo selectedBioC;
{ final RRepo repo= repos.getBioCMirror();
selectedBioC= (repo != null) ? Util.findRepo(this.allBioC, repo) : null;
this.requireConfirm &= ~REQUIRE_BIOC;
}
final SelectedRepos previousSettings= this.selectedRepos;
final SelectedRepos newSettings= new SelectedRepos(
selectedRepos,
selectedCRAN,
previousSettings.getBioCVersion(), selectedBioC );
for (final RRepo repo : this.allRepos) {
if (repo instanceof RVarRepo) {
((RVarRepo) repo).updateURL(newSettings);
}
}
this.selectedRepos= newSettings;
savePrefs(newSettings);
if (!newSettings.equals(previousSettings)) {
this.requireLoad |= (REQUIRE_REPO_PKGS);
final Change event= new Change(this.rEnv);
event.repos= 1;
fireUpdate(event);
}
}
@Override
public RRepo getRepo(final String repoId) {
if (repoId.isEmpty()) {
return null;
}
RRepo repo= this.selectedRepos.getRepo(repoId);
if (repo == null) {
repo= RPkgUtils.getRepoById(this.allRepos, repoId);
}
return repo;
}
private void savePrefs(final SelectedRepos repos) {
if (this.rEnv.get(IREnvConfiguration.class) == null) {
return;
}
final IScopeContext prefs= InstanceScope.INSTANCE;
final IEclipsePreferences globalNode= prefs.getNode(PREF_QUALIFIER);
final IEclipsePreferences envNode= prefs.getNode(this.selectedReposPref.getQualifier());
PreferenceUtils.setPrefValue(envNode, this.selectedReposPref, repos.getRepos());
PreferenceUtils.setPrefValue(envNode, this.selectedCRANPref, repos.getCRANMirror());
PreferenceUtils.setPrefValue(envNode, this.bioCVersionPref, repos.getBioCVersion());
PreferenceUtils.setPrefValue(envNode, this.selectedBioCPref, repos.getBioCMirror());
if (repos.getCRANMirror() != null) {
PreferenceUtils.setPrefValue(globalNode, LAST_CRAN_PREF, repos.getCRANMirror());
}
if (repos.getBioCMirror() != null) {
PreferenceUtils.setPrefValue(globalNode, LAST_BIOC_PREF, repos.getBioCMirror());
}
try {
globalNode.flush();
envNode.flush();
}
catch (final BackingStoreException e) {
RCorePlugin.logError("An error occurred when saving the R package manager preferences.", e);
}
}
@Override
public ImList<RRepo> getAvailableCRANMirrors() {
return this.allCRAN;
}
@Override
public List<RRepo> getAvailableBioCMirrors() {
return this.allBioC;
}
@Override
public RuntimeRLibPaths getRLibPaths() {
return this.rLibPaths;
}
private RuntimeRLibPathsLoader getRLibLoader() {
if (this.rLibGroups != null) {
return this.rLibGroups;
}
final IREnvConfiguration rEnvConfig= this.rEnv.get(IREnvConfiguration.class);
if (rEnvConfig != null) {
return new RuntimeRLibPathsLoader(rEnvConfig);
}
return null;
}
@Override
public @Nullable IRPkgSet getDataset() {
return (this.datasetExt != null) ? this.datasetExt : this.dataset;
}
@Override
public IRPkgSet.Ext getExtRPkgSet() {
return this.datasetExt;
}
@Override
public List<? extends IRView> getRViews() {
return this.rViews;
}
// @Override
// public List<? extends IRView> getBioCViews() {
// return biocViews;
// }
@Override
public void apply(final Tool process) {
process.getQueue().add(new AbstractRToolRunnable("r/renv/rpkg.apply", //$NON-NLS-1$
"Perform Package Manager Operations") {
@Override
protected void run(final RToolService r, final ProgressMonitor m) throws StatusException {
runApply(r, m);
}
});
}
private boolean beginRTaskSilent(final RToolService r, final ProgressMonitor m) {
synchronized (this) {
if (this.rProcess != null) {
return false;
}
this.rProcess= r.getTool();
this.rTask= 1;
return true;
}
}
private void beginRTask(final RToolService r, final ProgressMonitor m) throws StatusException {
synchronized (this) {
while (this.rProcess != null) {
if (this.rTask == 1) {
m.beginSubTask("Waiting for package check...");
try {
wait();
}
catch (final InterruptedException e) {
if (m.isCanceled()) {
throw new StatusException(Statuses.CANCEL_STATUS);
}
}
}
else {
final Status status= new ErrorStatus(RCore.BUNDLE_ID,
NLS.bind("Another package manager task for ''{0}'' is already running in ''{0}''",
this.rEnv.getName(), this.rProcess.getLabel(Tool.DEFAULT_LABEL) ));
// r.handleStatus(status, monitor);
throw new StatusException(status);
}
}
this.rProcess= r.getTool();
this.rTask= 2;
}
}
private void endRTask() {
synchronized (this) {
this.rProcess= null;
this.rTask= 0;
notifyAll();
}
}
protected void runApply(final RService r, final ProgressMonitor m) throws StatusException {
beginRTask((RToolService) r, m);
try {
final ISelectedRepos selectedRepos;
getReadLock().lock();
try {
selectedRepos= this.selectedRepos;
}
finally {
getReadLock().unlock();
}
if (getReposStatus(selectedRepos).getSeverity() != Status.ERROR) {
updateRequireLoad();
this.rTaskEvent= new Change(this.rEnv);
runApplyRepo(selectedRepos, r, m);
runLoadPkgs(selectedRepos, r, m);
fireUpdate(this.rTaskEvent);
}
}
finally {
this.rTaskEvent= null;
endRTask();
}
}
private ISelectedRepos runLoadRepos(final RService r, final ProgressMonitor m) throws StatusException {
try {
String bioCVersion= null;
try {
final RObject data= r.evalData("as.character(rj:::renv.getBioCVersion())", m);
bioCVersion= RDataUtils.checkSingleCharValue(data);
}
catch (final StatusException e) {
try {
final RObject data= r.evalData("as.character(tools:::.BioC_version_associated_with_R_version)", m);
bioCVersion= RDataUtils.checkSingleCharValue(data);
}
catch (final StatusException ignore) {
RCorePlugin.logError("Failed to get the version of BioC.", e);
}
}
final boolean loadRMirrors= ((this.requireLoad & (REQUIRE_CRAN | REQUIRE_BIOC)) != 0);
final boolean loadRRepos= ((this.requireLoad & (REQUIRE_REPOS)) != 0);
List<RRepoMirror> rCRANMirrors= null;
List<RRepoMirror> rBioCMirrors= null;
String selectedCRAN= null;
String selectedBioC= null;
if (loadRMirrors) {
m.beginSubTask("Fetching available mirrors...");
final String mirrorArgs= "all= FALSE, local.only= FALSE"; //$NON-NLS-1$
rCRANMirrors= fetchMirrors("getCRANmirrors(" + mirrorArgs + ')', r, m); //$NON-NLS-1$
try {
rBioCMirrors= fetchMirrors("utils:::.getMirrors('https://bioconductor.org/BioC_mirrors.csv', file.path(R.home('doc'), 'BioC_mirrors.csv'), " + mirrorArgs + ')', r, m); //$NON-NLS-1$
}
catch (final Exception e) {
}
if (rBioCMirrors == null || rBioCMirrors.isEmpty()) {
final String[][] s= new String[][] {
{ "United States (Seattle)", "http://www.bioconductor.org", "us" },
{ "United States (Rockville)", "http://watson.nci.nih.gov/bioc_mirror", "us" },
{ "Germany (Dortmund)", "http://bioconductor.statistik.tu-dortmund.de", "de" },
{ "China (Anhui)", "http://mirrors.ustc.edu.cn/bioc/", "cn" },
{ "United Kingdom (Hinxton)", "http://mirrors.ebi.ac.uk/bioconductor/", "uk" },
{ "Riken, Kobe (Japan)", "http://bioconductor.jp/", "jp" },
{ "Australia (Sydney)", "http://mirror.aarnet.edu.au/pub/bioconductor/", "au" },
{ "Brazil (Ribeirão Preto)", "http://bioconductor.fmrp.usp.br/", "br" },
};
rBioCMirrors= new ArrayList<>(s.length);
for (int i= 0; i < s.length; i++) {
final String url= Util.checkURL(s[i][1]);
if (!url.isEmpty()) {
rBioCMirrors.add(new RRepoMirror(RRepo.R_PREFIX + url, s[i][0], url, s[i][2]));
}
}
}
}
List<RRepo> rrepos= null;
List<RRepo> selected= null;
if (loadRRepos) {
m.beginSubTask("Fetching available repositories...");
{ final RObject data= r.evalData("options('repos')[[1L]]", m); //$NON-NLS-1$
if (data.getRObjectType() != RObject.TYPE_NULL) {
final RCharacterStore urls= RDataUtils.checkRCharVector(data).getData();
final RStore<?> ids= ((RVector<?>) data).getNames();
final int l= RDataUtils.checkIntLength(urls);
selected= new ArrayList<>(l);
for (int i= 0; i < l; i++) {
final String id= (ids != null) ? ids.getChar(i) : null;
final String url= urls.getChar(i);
final RRepo repo= Util.createRepoFromR(id, null, url);
if (repo != null) {
selected.add(repo);
}
}
}
else {
selected= new ArrayList<>(4);
}
}
final RObject data= r.evalData("local({" + //$NON-NLS-1$
"p <- file.path(Sys.getenv('HOME'), '.R', 'repositories')\n" + //$NON-NLS-1$
"if (!file.exists(p)) p <- file.path(R.home('etc'), 'repositories')\n" + //$NON-NLS-1$
"r <- utils::read.delim(p, header= TRUE, comment.char= '#', colClasses= c(rep.int('character', 3L), rep.int('logical', 4L)))\n" + //$NON-NLS-1$
"r[c(names(r)[1L], 'URL', 'default')]\n" + //$NON-NLS-1$
"})", m); //$NON-NLS-1$
final RDataFrame df= RDataUtils.checkRDataFrame(data);
final RStore<?> ids= df.getRowNames();
final RCharacterStore labels= RDataUtils.checkRCharVector(df.get(0)).getData();
final RCharacterStore urls= RDataUtils.checkRCharVector(df.get("URL")).getData(); //$NON-NLS-1$
final RLogicalStore isDefault= (selected.isEmpty()) ?
RDataUtils.checkRLogiVector(df.get("default")).getData() : null; //$NON-NLS-1$
{ final int l= RDataUtils.checkIntLength(labels);
rrepos= new ArrayList<>(l + 4);
for (int i= 0; i < l; i++) {
final String id= (ids != null) ? ids.getChar(i) : null;
final String url= urls.getChar(i);
final RRepo repo= Util.createRepoFromR(id, labels.getChar(i), url);
if (repo != null) {
rrepos.add(repo);
if (isDefault != null && isDefault.getLogi(i)) {
selected.add(repo);
}
}
}
}
for (int i= 0; i < selected.size(); i++) {
final RRepo repo= selected.get(i);
RRepo rrepo= null;
if (!repo.getURL().isEmpty()) {
rrepo= RPkgUtils.getRepoByURL(rrepos, repo.getURL());
}
if (rrepo != null) {
selected.set(i, rrepo);
continue;
}
if (!repo.getId().isEmpty()) {
final int j= rrepos.indexOf(repo); // by id
if (j >= 0) {
rrepo= rrepos.get(j);
if (!RVarRepo.hasVars(rrepo.getURL())) {
rrepo.setURL(repo.getURL());
}
selected.set(i, rrepo);
continue;
}
}
repo.setName(RRepo.hintName(repo));
continue;
}
}
if (loadRMirrors) {
if (loadRRepos) {
final RRepo repo= RPkgUtils.getRepoById(rrepos, RRepo.CRAN_ID);
if (repo != null && !repo.getURL().isEmpty()
&& !RVarRepo.hasVars(repo.getURL()) ) {
selectedCRAN= repo.getURL();
}
}
else {
final RObject data= r.evalData("options('repos')[[1L]]['CRAN']", m); //$NON-NLS-1$
if (data.getRObjectType() != RObject.TYPE_NULL) {
final String url= Util.checkURL(RDataUtils.checkSingleChar(data));
if (!url.isEmpty() && !RVarRepo.hasVars(url)) {
selectedCRAN= url;
}
}
}
{ final RObject data= r.evalData("options('BioC_mirror')[[1L]]", m); //$NON-NLS-1$
if (data.getRObjectType() != RObject.TYPE_NULL) {
selectedBioC= RDataUtils.checkSingleChar(data);
}
}
}
getWriteLock().lock();
try {
this.bioCVersion= bioCVersion;
if (loadRMirrors) {
this.requireLoad &= ~(REQUIRE_CRAN | REQUIRE_BIOC);
this.rCRANMirrors= rCRANMirrors;
this.selectedCRANInR= selectedCRAN;
this.rBioCMirrors= rBioCMirrors;
this.selectedBioCInR= selectedBioC;
this.mirrorsStamp= this.rTaskEvent.stamp;
checkMirrors(this.rTaskEvent);
}
if (loadRRepos) {
this.requireLoad &= ~(REQUIRE_REPOS);
this.rRepos= rrepos;
this.selectedReposInR= selected;
checkRepos(this.rTaskEvent);
}
this.firstTime= false;
if (getReposStatus(this.selectedRepos, this.selectedRepos, this.requireConfirm)
.getSeverity() == Status.OK ) {
return this.selectedRepos;
}
return null;
}
finally {
getWriteLock().unlock();
}
}
catch (final Exception e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID, 0,
"An error occurred when loading data for package manager.", e) );
}
}
private List<RRepoMirror> fetchMirrors(final String rExpr, final RService r,
final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
final RObject data= r.evalData(rExpr + "[c('Name', 'URL', 'CountryCode')]", m); //$NON-NLS-1$
final RDataFrame df= RDataUtils.checkRDataFrame(data);
final RCharacterStore names= RDataUtils.checkRCharVector(df.get("Name")).getData(); //$NON-NLS-1$
final RCharacterStore urls= RDataUtils.checkRCharVector(df.get("URL")).getData(); //$NON-NLS-1$
final RCharacterStore countryCodes= RDataUtils.checkRCharVector(df.get("CountryCode")).getData(); //$NON-NLS-1$
final int l= RDataUtils.checkIntLength(names);
final List<RRepoMirror> mirrors= new ArrayList<>(l);
for (int i= 0; i < l; i++) {
final String url= Util.checkURL(urls.getChar(i));
if (!url.isEmpty()) {
mirrors.add(new RRepoMirror(RRepo.R_PREFIX + url, names.getChar(i), url,
countryCodes.getChar(i) ));
}
}
return mirrors;
}
private RRepoMirror getRegionMirror(final List<RRepoMirror> mirrors) {
final String countryCode= Locale.getDefault().getCountry().toLowerCase();
RRepoMirror http= null;
for (final RRepoMirror repo : mirrors) {
if (countryCode.equals(repo.getCountryCode())) {
if (repo.getURL().startsWith("https:")) { //$NON-NLS-1$
return repo;
}
else if (http == null) {
http= repo;
}
}
}
return http;
}
private void runApplyRepo(final ISelectedRepos repos,
final RService r, final ProgressMonitor m) throws StatusException {
m.beginSubTask("Setting repository configuration...");
try {
if (repos.getBioCMirror() != null) {
final FunctionCall call= r.createFunctionCall("options");
call.addChar("BioC_mirror", repos.getBioCMirror().getURL());
call.evalVoid(m);
}
{ final List<RRepo> selectedRepos= (List<RRepo>) repos.getRepos();
final String[] ids= new String[selectedRepos.size()];
final String[] urls= new String[selectedRepos.size()];
for (int i= 0; i < urls.length; i++) {
final RRepo repo= selectedRepos.get(i);
ids[i]= repo.getId();
urls[i]= repo.getURL();
}
final RVector<RCharacterStore> data= new RVectorImpl<>(
new RCharacter32Store(urls), RObject.CLASSNAME_CHARACTER, ids);
final FunctionCall call= r.createFunctionCall("options");
call.add("repos", data);
call.evalVoid(m);
}
}
catch (final StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when setting repository configuration in R.",
e ));
}
}
private void runLoadPkgs(final ISelectedRepos repoSettings,
final RService r, final ProgressMonitor m) throws StatusException {
final boolean loadRepoPkgs= ((this.requireLoad & (REQUIRE_REPO_PKGS)) != 0
&& getReposStatus(repoSettings).getSeverity() == Status.OK );
boolean loadInstPkgs= ((this.requireLoad & (REQUIRE_INST_PKGS)) != 0);
RPkgCompilation<IRPkgData> available= null;
if (loadRepoPkgs) {
available= this.pkgScanner.loadAvailable(this.rEnv, repoSettings, r, m);
loadInstPkgs= true; // to combine in package
}
if (loadInstPkgs) {
checkInstalled(true, available, r, m);
}
if (loadRepoPkgs || loadInstPkgs) {
getWriteLock().lock();
try {
setPkgs();
if (loadRepoPkgs) {
this.requireLoad &= ~REQUIRE_REPO_PKGS;
this.pkgsStamp= this.rTaskEvent.stamp;
this.rTaskEvent.pkgs |= AVAILABLE_PKGS;
}
if (loadInstPkgs) {
this.requireLoad &= ~REQUIRE_INST_PKGS;
}
if (this.datasetExt != null) {
checkRViews(this.datasetExt, r, m);
}
}
finally {
getWriteLock().unlock();
}
}
}
private void checkInstalled(final boolean fullUpdate, @Nullable RPkgCompilation<IRPkgData> newAvailable,
final RService r, final ProgressMonitor m) throws StatusException {
RVector<RNumericStore> libs= null;
boolean[] update= null;
final RuntimeRLibPathsLoader rLibLoader= getRLibLoader();
try {
libs= rLibLoader.loadLibStamps(r, m);
final int l= RDataUtils.checkIntLength(libs.getData());
ITER_LIBS: for (int idxLib= 0; idxLib < l; idxLib++) {
final String libPath= libs.getNames().getChar(idxLib);
if (this.libs != null) {
final int idx= (int) this.libs.getNames().indexOf(libPath);
if (idx >= 0) {
if (this.libs.getData().getNum(idx) == libs.getData().getNum(idxLib)) {
continue ITER_LIBS;
}
}
}
if (update == null) {
update= new boolean[l];
}
update[idxLib]= true;
}
this.libs= libs;
}
catch (final UnexpectedRDataException | StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when checking for changed R libraries.",
e ));
}
if (update != null || fullUpdate) {
if (this.rTaskEvent == null) {
this.rTaskEvent= new Change(this.rEnv);
}
if (this.rTaskEvent.oldPkgs == null) {
this.rTaskEvent.oldPkgs= getDataset();
}
if (this.datasetExt != null || fullUpdate) {
this.rLibPaths= rLibLoader.load(libs, RuntimeRLibPathsLoader.FULL_CHECK, r, m);
if (newAvailable == null) {
newAvailable= (this.datasetExt != null) ?
this.datasetExt.getAvailable() :
new BasicRPkgCompilation<>(0);
}
final FullRPkgSet newPkgs= new FullRPkgSet(this.rEnv, this.rLibPaths, newAvailable);
this.pkgScanner.updateInstFull(this.rLibPaths, update, newPkgs, this.rTaskEvent, r, m);
this.rTaskEvent.newPkgs= newPkgs;
}
else {
this.rLibPaths= rLibLoader.load(libs, 0, r, m);
this.rTaskEvent.newPkgs= new RPkgSet(this.rEnv, this.rLibPaths,
this.pkgScanner.loadInstalled(this.rLibPaths, update, this.rTaskEvent, r, m) );
}
if (this.rTaskEvent.installedPkgs != null && this.rTaskEvent.installedPkgs.names.isEmpty()) {
this.rTaskEvent.installedPkgs= null;
}
if (!fullUpdate) {
setPkgs();
}
if (this.rTaskEvent.installedPkgs != null && this.db != null) {
this.db.updatePkgs(this.rTaskEvent);
}
if (!fullUpdate && this.datasetExt != null) {
checkRViews(this.datasetExt, r, m);
}
}
}
private void setPkgs() {
final Change event= this.rTaskEvent;
if (event.newPkgs instanceof FullRPkgSet) {
this.datasetExt= (FullRPkgSet) event.newPkgs;
this.dataset= null;
}
else if (event.newPkgs instanceof RPkgSet) {
this.datasetExt= null;
this.dataset= (RPkgSet) event.newPkgs;
}
if (event.installedPkgs != null) {
event.pkgs |= INSTALLED;
}
}
private void checkRViews(final FullRPkgSet pkgs,
final RService r, final ProgressMonitor m) {
final RPkgInfoAndData pkg= pkgs.getInstalled().getFirst("ctv"); //$NON-NLS-1$
if (pkg == null || pkg.getVersion().equals(this.rViewsVersion)) {
return;
}
final List<RView> rViews= RViewTasks.loadRViews(r, m);
if (rViews != null) {
this.rViews= rViews;
this.rViewsVersion= pkg.getVersion();
this.rTaskEvent.views= 1;
}
}
@Override
public IRPkgData addToCache(final IFileStore store, final IProgressMonitor monitor) throws CoreException {
try {
final RPkg pkg= RPkgUtils.checkPkgFileName(store.getName());
final RPkgType type= RPkgUtils.checkPkgType(store.getName(), this.rPlatform);
this.cache.add(pkg.getName(), type, store, monitor);
return new RPkgData(pkg.getName(), RNumVersion.NONE, RRepo.WS_CACHE_PREFIX + type.name().toLowerCase());
}
catch (final StatusException e) {
throw StatusUtils.convert(e);
}
}
@Override
public void perform(final Tool rTool, final List<? extends RPkgAction> actions) {
if (actions.isEmpty()) {
return;
}
final String label= (actions.get(0).getAction() == RPkgAction.UNINSTALL) ?
"Uninstall R Packages" : "Install/Update R Packages";
final RPkgOperator op= new RPkgOperator(this);
rTool.getQueue().add(new AbstractStatetRRunnable("r/renv/pkgs.inst", label) { //$NON-NLS-1$
@Override
protected void run(final IRConsoleService r, final ProgressMonitor m) throws StatusException {
beginRTask(r, m);
try {
checkNewCommand(r, m);
r.briefAboutToChange();
op.runActions(actions, r, m);
}
catch (final UnexpectedRDataException | StatusException e) {
throw new StatusException(new ErrorStatus(RCore.BUNDLE_ID,
"An error occurred when installing and updating R packages.",
e ));
}
finally {
endRTask();
r.briefChanged(IRConsoleService.PACKAGE_CHANGE);
}
}
});
}
@Override
public void loadPkgs(final Tool rTool, final List<? extends IRPkgInfoAndData> pkgs,
final boolean expliciteLocation) {
final RPkgOperator op= new RPkgOperator(this);
rTool.getQueue().add(new AbstractStatetRRunnable("r/renv/pkgs.load", //$NON-NLS-1$
"Load R Packages") {
@Override
protected void run(final IRConsoleService r, final ProgressMonitor m) throws StatusException {
checkNewCommand(r, m);
op.loadPkgs(pkgs, expliciteLocation, r, m);
}
});
}
}