blob: decbd89060c3bba6ae93f5601c13b7f46e33c4d2 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.sdk.remote;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.tools.idea.sdk.remote.internal.AddonsListFetcher;
import com.android.tools.idea.sdk.remote.internal.AddonsListFetcher.Site;
import com.android.tools.idea.sdk.remote.internal.DownloadCache;
import com.android.tools.idea.sdk.remote.internal.ITaskMonitor;
import com.android.tools.idea.sdk.remote.internal.NullTaskMonitor;
import com.android.tools.idea.sdk.remote.internal.sources.*;
import com.android.tools.idea.sdk.remote.internal.updater.SettingsController;
import com.android.utils.ILogger;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
/**
* This class keeps information on the remote SDK repository.
*/
public class RemoteSdk {
/**
* Default expiration delay is 24 hours.
*/
public final static long DEFAULT_EXPIRATION_PERIOD_MS = 24 * 3600 * 1000;
private final SettingsController mSettingsController;
private final SdkSources mSdkSources = new SdkSources();
private long mSdkSourceTS;
private DownloadCache mDownloadCache;
public RemoteSdk() {
mSettingsController = SettingsController.getInstance();
}
/**
* Fetches the remote list of packages.
* <p/>
* This respects the settings from the {@link SettingsController} which
* dictates whether the {@link DownloadCache} is used and whether HTTP
* is enforced over HTTPS.
* <p/>
* The call may block on network access. Callers will likely want to invoke this
* from a thread and make sure the logger is thread-safe with regard to UI updates.
*
* @param sources The sources to download from.
* @param logger A logger to report status & progress.
* @return A non-null map of {@link PkgType} to {@link RemotePkgInfo}
* describing the remote packages available for install/download.
*/
@NonNull
public Multimap<PkgType, RemotePkgInfo> fetch(@NonNull SdkSources sources, @NonNull ILogger logger) {
Multimap<PkgType, RemotePkgInfo> remotes = HashMultimap.create();
boolean forceHttp = mSettingsController.getForceHttp();
// Implementation detail: right now this reuses the SdkSource(s) classes
// from the sdk-repository v2. The problem with that is that the sources are
// mutable and hold the fetch logic and hold the packages array.
// Instead I'd prefer to have the sources be immutable descriptors and move
// the fetch logic here. Eventually my goal is to get rid of them
// and include the logic directly here instead but for right now lets
// just start with what we have to avoid implementing it all at once.
for (SdkSource source : sources.getAllSources()) {
source.load(getDownloadCache(), new NullTaskMonitor(logger), forceHttp);
RemotePkgInfo[] pkgs = source.getPackages();
if (pkgs != null) { // pkgs will be null if there was an error during load
for (RemotePkgInfo p : pkgs) {
remotes.put(p.getPkgDesc().getType(), p);
}
}
}
return remotes;
}
/**
* Returns the {@link SdkSources} object listing all sources to load from.
* This includes the main repository.xml, the main addon.xml as well as all the
* add-ons or sys-img xmls listed in the addons-list.xml.
* <p/>
* The method caches the last access and only refresh it if data is either not
* present or the expiration time has be passed.
*
* @param expirationDelayMs The expiration delay in milliseconds.
* Use {@link #DEFAULT_EXPIRATION_PERIOD_MS} by default.
* @param logger A non-null object to log messages. TODO change to an ITaskMonitor
* to be able to update the caller's progress bar UI, if any.
* @return A non-null {@link SdkSources}
*/
@NonNull
public SdkSources fetchSources(long expirationDelayMs, @NonNull ILogger logger) {
long now = System.currentTimeMillis();
boolean expired = (now - mSdkSourceTS) > expirationDelayMs;
// Load the conventional sources.
// For testing, the env var can be set to replace the default root download URL.
// It must end with a / and its the location where the updater will look for
// the repository.xml, addons_list.xml and such files.
if (expired || !mSdkSources.sourcesLoaded(SdkSourceCategory.ANDROID_REPO)) {
String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
if (baseUrl == null || baseUrl.length() <= 0 || !baseUrl.endsWith("/")) { //$NON-NLS-1$
baseUrl = SdkRepoConstants.URL_GOOGLE_SDK_SITE;
}
mSdkSources.set(SdkSourceCategory.ANDROID_REPO,
Lists.<SdkSource>newArrayList(new SdkRepoSource(baseUrl, SdkSourceCategory.ANDROID_REPO.getUiName())));
}
// Load user sources (this will also notify change listeners but this operation is
// done early enough that there shouldn't be any anyway.)
// Don't refresh sources, as this doesn't really change except within the sdk manager, which manages refreshes separately.
if (!mSdkSources.sourcesLoaded(SdkSourceCategory.USER_ADDONS)) {
mSdkSources.loadUserAddons(logger);
}
if (expired || !mSdkSources.sourcesLoaded(SdkSourceCategory.ADDONS_3RD_PARTY)) {
ITaskMonitor tempMonitor = new NullTaskMonitor(logger);
String url = SdkAddonsListConstants.URL_ADDON_LIST;
// We override SdkRepoConstants.URL_GOOGLE_SDK_SITE if this is defined
String baseUrl = System.getenv("SDK_TEST_BASE_URL"); //$NON-NLS-1$
if (baseUrl != null) {
if (baseUrl.length() > 0 && baseUrl.endsWith("/")) { //$NON-NLS-1$
if (url.startsWith(SdkRepoConstants.URL_GOOGLE_SDK_SITE)) {
url = baseUrl + url.substring(SdkRepoConstants.URL_GOOGLE_SDK_SITE.length());
}
}
else {
tempMonitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
}
}
if (mSettingsController.getForceHttp()) {
url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
}
// Hook to bypass loading 3rd party addons lists.
boolean fetch3rdParties = System.getenv("SDK_SKIP_3RD_PARTIES") == null;
AddonsListFetcher fetcher = new AddonsListFetcher();
Site[] sites = fetcher.fetch(url, getDownloadCache(), tempMonitor);
if (sites != null) {
mSdkSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
if (fetch3rdParties) {
ArrayList<SdkSource> newSources = Lists.newArrayList();
for (Site s : sites) {
switch (s.getType()) {
case ADDON_SITE:
newSources.add(new SdkAddonSource(s.getUrl(), s.getUiName()));
break;
case SYS_IMG_SITE:
newSources.add(new SdkSysImgSource(s.getUrl(), s.getUiName()));
break;
}
}
mSdkSources.set(SdkSourceCategory.ADDONS_3RD_PARTY, newSources);
}
}
}
mSdkSourceTS = now;
return mSdkSources;
}
/**
* Returns the {@link DownloadCache}
* Extracted so that we can override this in unit tests.
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
protected DownloadCache getDownloadCache() {
if (mDownloadCache == null) {
mDownloadCache = new DownloadCache(
mSettingsController.getUseDownloadCache() ? DownloadCache.Strategy.FRESH_CACHE : DownloadCache.Strategy.DIRECT);
}
return mDownloadCache;
}
}