blob: 7a3da42049bdf30c8203bcf6559dbaba8963bef6 [file] [log] [blame]
/*
* Copyright (C) 2011 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.sdklib.internal.repository.updater;
import com.android.annotations.NonNull;
import com.android.sdklib.internal.repository.AddonsListFetcher;
import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
import com.android.sdklib.internal.repository.DownloadCache;
import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.NullTaskMonitor;
import com.android.sdklib.internal.repository.archives.Archive;
import com.android.sdklib.internal.repository.packages.Package;
import com.android.sdklib.internal.repository.packages.Package.UpdateInfo;
import com.android.sdklib.internal.repository.sources.SdkAddonSource;
import com.android.sdklib.internal.repository.sources.SdkSource;
import com.android.sdklib.internal.repository.sources.SdkSourceCategory;
import com.android.sdklib.internal.repository.sources.SdkSources;
import com.android.sdklib.internal.repository.sources.SdkSysImgSource;
import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Loads packages fetched from the remote SDK Repository and keeps track
* of their state compared with the current local SDK installation.
*
* @deprecated
* com.android.sdklib.internal.repository has moved into Studio as
* com.android.tools.idea.sdk.remote.internal.
*/
@Deprecated
public class PackageLoader {
/** The update data context. Never null. */
private final UpdaterData mUpdaterData;
/**
* The {@link DownloadCache} override. Can be null, in which case the one from
* {@link UpdaterData} is used instead.
* @see #getDownloadCache()
*/
private final DownloadCache mOverrideCache;
/**
* 0 = need to fetch remote addons list once..
* 1 = fetch succeeded, don't need to do it any more.
* -1= fetch failed, do it again only if the user requests a refresh
* or changes the force-http setting.
*/
private int mStateFetchRemoteAddonsList;
/**
* Interface for the callback called by
* {@link PackageLoader#loadPackages(boolean, ISourceLoadedCallback)}.
* <p/>
* After processing each source, the package loader calls {@link #onUpdateSource}
* with the list of packages found in that source.
* By returning true from {@link #onUpdateSource}, the client tells the loader to
* continue and process the next source. By returning false, it tells to stop loading.
* <p/>
* The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no
* matter how the loader stopped, so that the client can clean up or perform any
* final action.
*/
public interface ISourceLoadedCallback {
/**
* After processing each source, the package loader calls this method with the
* list of packages found in that source.
* By returning true from {@link #onUpdateSource}, the client tells
* the loader to continue and process the next source.
* By returning false, it tells to stop loading.
* <p/>
* <em>Important</em>: This method is called from a sub-thread, so clients which
* try to access any UI widgets must wrap their calls into
* {@code Display.syncExec(Runnable)} or {@code Display.asyncExec(Runnable)}.
*
* @param packages All the packages loaded from the source. Never null.
* @return True if the load operation should continue, false if it should stop.
*/
boolean onUpdateSource(SdkSource source, Package[] packages);
/**
* This method is guaranteed to be called at the end, no matter how the
* loader stopped, so that the client can clean up or perform any final action.
*/
void onLoadCompleted();
}
/**
* Interface describing the task of installing a specific package.
* For details on the operation,
* see {@link PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)}.
*
* @see PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)
*/
public interface IAutoInstallTask {
/**
* Invoked by the loader once a source has been loaded and its package
* definitions are known. The method should return the {@code packages}
* array and can modify it if necessary.
* The loader will call {@link #acceptPackage(Package)} on all the packages returned.
*
* @param source The source of the packages. Null for the locally installed packages.
* @param packages The packages found in the source.
*/
Package[] filterLoadedSource(SdkSource source, Package[] packages);
/**
* Called by the install task for every package available (new ones, updates as well
* as existing ones that don't have a potential update.)
* The method should return true if this is a package that should be installed.
* <p/>
* <em>Important</em>: This method is called from a sub-thread, so clients who try
* to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)}
* or {@code Display.asyncExec(Runnable)}.
*/
boolean acceptPackage(Package pkg);
/**
* Called when the accepted package has been installed, successfully or not.
* If an already installed (aka existing) package has been accepted, this will
* be called with a 'true' success and the actual install paths.
* <p/>
* <em>Important</em>: This method is called from a sub-thread, so clients who try
* to access any UI widgets must wrap their calls into {@code Display.syncExec(Runnable)}
* or {@code Display.asyncExec(Runnable)}.
*/
void setResult(boolean success, Map<Package, File> installPaths);
/**
* Called when the task is done iterating and completed.
*/
void taskCompleted();
}
/**
* Creates a new PackageManager associated with the given {@link UpdaterData}
* and using the {@link UpdaterData}'s default {@link DownloadCache}.
*
* @param updaterData The {@link UpdaterData}. Must not be null.
*/
public PackageLoader(UpdaterData updaterData) {
mUpdaterData = updaterData;
mOverrideCache = null;
}
/**
* Creates a new PackageManager associated with the given {@link UpdaterData}
* but using the specified {@link DownloadCache} instead of the one from
* {@link UpdaterData}.
*
* @param updaterData The {@link UpdaterData}. Must not be null.
* @param cache The {@link DownloadCache} to use instead of the one from {@link UpdaterData}.
*/
public PackageLoader(UpdaterData updaterData, DownloadCache cache) {
mUpdaterData = updaterData;
mOverrideCache = cache;
}
public UpdaterData getUpdaterData() {
return mUpdaterData;
}
/**
* Runs a runnable on the UI thread.
* The base implementation just runs the runnable right away.
*
* @param r Non-null runnable.
*/
protected void runOnUiThread(@NonNull Runnable r) {
r.run();
}
/**
* Loads all packages from the remote repository.
* This runs in an {@link ITask}. The call is blocking.
* <p/>
* The callback is called with each set of {@link PkgItem} found in each source.
* The caller is responsible to accumulate the packages given to the callback
* after each source is finished loaded. In return the callback tells the loader
* whether to continue loading sources.
* <p/>
* Normally this method doesn't access the remote source if it's already
* been loaded in the in-memory source (e.g. don't fetch twice).
*
* @param overrideExisting Set this to true when the caller wants to
* check for updates and discard any existing source already
* loaded in memory. It should be false for normal use.
* @param sourceLoadedCallback The callback to invoke for each loaded source.
*/
public void loadPackages(
final boolean overrideExisting,
final ISourceLoadedCallback sourceLoadedCallback) {
try {
if (mUpdaterData == null) {
return;
}
mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
@Override
public void run(ITaskMonitor monitor) {
monitor.setProgressMax(10);
// get local packages and offer them to the callback
Package[] localPkgs =
mUpdaterData.getInstalledPackages(monitor.createSubMonitor(1));
if (localPkgs == null) {
localPkgs = new Package[0];
}
if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) {
return;
}
// get remote packages
boolean forceHttp =
mUpdaterData.getSettingsController().getSettings().getForceHttp();
loadRemoteAddonsList(monitor.createSubMonitor(1));
SdkSource[] sources = mUpdaterData.getSources().getAllSources();
try {
if (sources != null && sources.length > 0) {
ITaskMonitor subMonitor = monitor.createSubMonitor(8);
subMonitor.setProgressMax(sources.length);
for (SdkSource source : sources) {
Package[] pkgs = source.getPackages();
if (pkgs == null || overrideExisting) {
source.load(getDownloadCache(),
subMonitor.createSubMonitor(1),
forceHttp);
pkgs = source.getPackages();
}
if (pkgs == null) {
continue;
}
// Notify the callback a new source has finished loading.
// If the callback requests so, stop right away.
if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) {
return;
}
}
}
} catch(Exception e) {
monitor.logError("Loading source failed: %1$s", e.toString());
} finally {
monitor.setDescription("Done loading packages.");
}
}
});
} finally {
sourceLoadedCallback.onLoadCompleted();
}
}
/**
* Load packages, source by source using
* {@link #loadPackages(boolean, ISourceLoadedCallback)},
* and executes the given {@link IAutoInstallTask} on the current package list.
* That is for each package known, the install task is queried to find if
* the package is the one to be installed or updated.
* <p/>
* - If an already installed package is accepted by the task, it is returned. <br/>
* - If a new package (remotely available but not installed locally) is accepted,
* the user will be <em>prompted</em> for permission to install it. <br/>
* - If an existing package has updates, the install task will be accept if it
* accepts one of the updating packages, and if yes the the user will be
* <em>prompted</em> for permission to install it. <br/>
* <p/>
* Only one package can be accepted, after which the task is completed.
* There is no direct return value, {@link IAutoInstallTask#setResult} is called on the
* result of the accepted package.
* When the task is completed, {@link IAutoInstallTask#taskCompleted()} is called.
* <p/>
* The call is blocking. Although the name says "Task", this is not an {@link ITask}
* running in its own thread but merely a synchronous call.
*
* @param installFlags Flags for installation such as
* {@link UpdaterData#TOOLS_MSG_UPDATED_FROM_ADT}.
* @param installTask The task to perform.
*/
public void loadPackagesWithInstallTask(
final int installFlags,
final IAutoInstallTask installTask) {
loadPackages(false /*overrideExisting*/, new ISourceLoadedCallback() {
List<Archive> mArchivesToInstall = new ArrayList<Archive>();
Map<Package, File> mInstallPaths = new HashMap<Package, File>();
@Override
public boolean onUpdateSource(SdkSource source, Package[] packages) {
packages = installTask.filterLoadedSource(source, packages);
if (packages == null || packages.length == 0) {
// Tell loadPackages() to process the next source.
return true;
}
for (Package pkg : packages) {
if (pkg.isLocal()) {
// This is a local (aka installed) package
if (installTask.acceptPackage(pkg)) {
// If the caller is accepting an installed package,
// return a success and give the package's install path
Archive[] a = pkg.getArchives();
// an installed package should have one local compatible archive
if (a.length == 1 && a[0].isCompatible()) {
mInstallPaths.put(pkg, new File(a[0].getLocalOsPath()));
}
}
} else {
// This is a remote package
if (installTask.acceptPackage(pkg)) {
// The caller is accepting this remote package. We'll install it.
for (Archive archive : pkg.getArchives()) {
if (archive.isCompatible()) {
mArchivesToInstall.add(archive);
break;
}
}
}
}
}
// Tell loadPackages() to process the next source.
return true;
}
@Override
public void onLoadCompleted() {
if (!mArchivesToInstall.isEmpty()) {
installArchives(mArchivesToInstall);
}
if (mInstallPaths == null) {
installTask.setResult(false, null);
} else {
installTask.setResult(true, mInstallPaths);
}
installTask.taskCompleted();
}
/**
* Shows the UI of the install selector.
* If the package is then actually installed, refresh the local list and
* notify the install task of the installation path.
*
* @param archivesToInstall The archives to install.
*/
private void installArchives(final List<Archive> archivesToInstall) {
// Actually install the new archives that we just found.
// This will display some UI so we need a shell's sync exec.
final List<Archive> installedArchives = new ArrayList<Archive>();
runOnUiThread(new Runnable() {
@Override
public void run() {
List<Archive> archives =
mUpdaterData.updateOrInstallAll_WithGUI(
archivesToInstall,
true /* includeObsoletes */,
installFlags);
if (archives != null) {
installedArchives.addAll(archives);
}
}
});
if (installedArchives.isEmpty()) {
// We failed to install anything.
mInstallPaths = null;
return;
}
// The local package list has changed, make sure to refresh it
mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
mUpdaterData.getLocalSdkParser().clearPackages();
final Package[] localPkgs = mUpdaterData.getInstalledPackages(
new NullTaskMonitor(mUpdaterData.getSdkLog()));
// Scan the installed package list to find the install paths.
for (Archive installedArchive : installedArchives) {
Package pkg = installedArchive.getParentPackage();
for (Package localPkg : localPkgs) {
if (localPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
Archive[] localArchive = localPkg.getArchives();
if (localArchive.length == 1 && localArchive[0].isCompatible()) {
mInstallPaths.put(
localPkg,
new File(localArchive[0].getLocalOsPath()));
}
}
}
}
}
});
}
/**
* Loads the remote add-ons list.
*/
public void loadRemoteAddonsList(ITaskMonitor monitor) {
if (mStateFetchRemoteAddonsList != 0) {
return;
}
mUpdaterData.getTaskFactory().start("Load Add-ons List", monitor, new ITask() {
@Override
public void run(ITaskMonitor subMonitor) {
loadRemoteAddonsListInTask(subMonitor);
}
});
}
private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
mStateFetchRemoteAddonsList = -1;
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 {
monitor.logError("Ignoring invalid SDK_TEST_BASE_URL: %1$s", baseUrl); //$NON-NLS-1$
}
}
if (mUpdaterData.getSettingsController().getSettings().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(), monitor);
if (sites != null) {
SdkSources sources = mUpdaterData.getSources();
sources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
if (fetch3rdParties) {
for (Site s : sites) {
switch (s.getType()) {
case ADDON_SITE:
sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
new SdkAddonSource(s.getUrl(), s.getUiName()));
break;
case SYS_IMG_SITE:
sources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
new SdkSysImgSource(s.getUrl(), s.getUiName()));
break;
}
}
}
sources.notifyChangeListeners();
mStateFetchRemoteAddonsList = 1;
}
monitor.setDescription("Fetched Add-ons List successfully");
}
/**
* Returns the {@link DownloadCache} to use.
*
* @return Returns {@link #mOverrideCache} if not null; otherwise returns the
* one from {@link UpdaterData} is used instead.
*/
private DownloadCache getDownloadCache() {
return mOverrideCache != null ? mOverrideCache : mUpdaterData.getDownloadCache();
}
}