blob: 6f26a1474392450bb30cc1ff29e0f53ca3abc765 [file] [log] [blame]
/*
* Copyright (C) 2014 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;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.concurrency.GuardedBy;
import com.android.sdklib.repository.descriptors.PkgType;
import com.android.tools.idea.sdk.remote.RemotePkgInfo;
import com.android.tools.idea.sdk.remote.RemoteSdk;
import com.android.tools.idea.sdk.remote.internal.sources.SdkSources;
import com.android.utils.ILogger;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.reference.SoftReference;
import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.android.sdk.AndroidSdkData;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class SdkState {
public final static long DEFAULT_EXPIRATION_PERIOD_MS = TimeUnit.DAYS.toMillis(1);
private static final Logger LOG = Logger.getInstance("#com.android.tools.idea.sdk.SdkState");
@GuardedBy(value = "sSdkStates") private static final Set<SoftReference<SdkState>> sSdkStates = new HashSet<SoftReference<SdkState>>();
@Nullable private final AndroidSdkData mySdkData;
private final RemoteSdk myRemoteSdk;
private SdkPackages myPackages = new SdkPackages();
private long myLastRefreshMs;
private LoadTask myTask;
private final Object myTaskLock = new Object();
private SdkState(@Nullable AndroidSdkData sdkData) {
mySdkData = sdkData;
if (mySdkData == null) {
myPackages = new SdkPackages();
}
myRemoteSdk = new RemoteSdk();
}
/**
* This shouldn't be needed unless interacting with the internals of the remote sdk.
*
* @return
*/
@NonNull
public RemoteSdk getRemoteSdk() {
return myRemoteSdk;
}
@NonNull
public static SdkState getInstance(@Nullable AndroidSdkData sdkData) {
synchronized (sSdkStates) {
for (Iterator<SoftReference<SdkState>> it = sSdkStates.iterator(); it.hasNext(); ) {
SoftReference<SdkState> ref = it.next();
SdkState s = ref.get();
if (s == null) {
it.remove();
continue;
}
if (Objects.equal(s.mySdkData, sdkData)) {
return s;
}
}
SdkState s = new SdkState(sdkData);
sSdkStates.add(new SoftReference<SdkState>(s));
return s;
}
}
@Nullable
public AndroidSdkData getSdkData() {
return mySdkData;
}
@NonNull
public SdkPackages getPackages() {
return myPackages;
}
public boolean loadAsync(long timeoutMs,
boolean canBeCancelled,
@Nullable SdkLoadedCallback onLocalComplete,
@Nullable SdkLoadedCallback onSuccess,
@Nullable Runnable onError,
boolean forceRefresh) {
return load(timeoutMs, canBeCancelled, createList(onLocalComplete), createList(onSuccess), createList(onError), forceRefresh, false);
}
private boolean load(long timeoutMs,
boolean canBeCancelled,
@NonNull List<SdkLoadedCallback> onLocalComplete,
@NonNull List<SdkLoadedCallback> onSuccess,
@NonNull List<Runnable> onError,
boolean forceRefresh,
boolean sync) {
if (!forceRefresh && System.currentTimeMillis() - myLastRefreshMs < timeoutMs) {
for (SdkLoadedCallback localComplete : onLocalComplete) {
localComplete.run(myPackages);
}
for (SdkLoadedCallback success : onSuccess) {
success.run(myPackages);
}
return false;
}
synchronized (myTaskLock) {
if (myTask != null) {
myTask.addCallbacks(onLocalComplete, onSuccess, onError);
return false;
}
myTask = new LoadTask(canBeCancelled, onLocalComplete, onSuccess, onError, forceRefresh, sync);
}
if (!ApplicationManager.getApplication().isDispatchThread()) {
// not dispatch thread, assume progress is being handled elsewhere. Just run the task.
myTask.run(new EmptyProgressIndicator());
return true;
}
ProgressManager.getInstance().run(myTask);
return true;
}
public boolean loadSynchronously(long timeoutMs,
boolean canBeCancelled,
@Nullable SdkLoadedCallback onLocalComplete,
@Nullable SdkLoadedCallback onSuccess,
@Nullable final Runnable onError,
boolean forceRefresh) {
final Semaphore completed = new Semaphore();
completed.down();
List<SdkLoadedCallback> onLocalCompletes = createList(onLocalComplete);
List<SdkLoadedCallback> onSuccesses = createList(onSuccess);
List<Runnable> onErrors = createList(onError);
onSuccesses.add(new SdkLoadedCallback(false) {
@Override
public void doRun(@NotNull SdkPackages packages) {
completed.up();
}
});
onErrors.add(new Runnable() {
@Override
public void run() {
completed.up();
}
});
boolean result = load(timeoutMs, canBeCancelled, onLocalCompletes, onSuccesses, onErrors, forceRefresh, true);
if (!ApplicationManager.getApplication().isDispatchThread()) {
// Not dispatch thread, assume progress is being handled elsewhere.
if (result) {
// We don't have to wait since load() ran in-thread.
return true;
}
try {
completed.waitForUnsafe();
}
catch (InterruptedException e) {
if (onError != null) {
onError.run();
}
return false;
}
return true;
}
// If we are on the dispatch thread, show progress while waiting.
ProgressManager pm = ProgressManager.getInstance();
ProgressIndicator indicator = pm.getProgressIndicator();
boolean startedProgress;
indicator = (startedProgress = indicator == null) ? new ProgressWindow(false, false, null) : indicator;
if (startedProgress) {
indicator.start();
}
pm.executeProcessUnderProgress(new Runnable() {
@Override
public void run() {
boolean success = false;
ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true);
try {
completed.waitForUnsafe();
success = true;
}
catch (InterruptedException e) {
LOG.warn(e);
}
if (!success) {
if (onError != null) {
onError.run();
}
}
}
}, indicator);
if (startedProgress) {
indicator.stop();
}
return result;
}
@NonNull
private static <T> List<T> createList(@Nullable T r) {
if (r == null) {
return Lists.newArrayList();
}
return Lists.newArrayList(r);
}
// -----
private static class IndicatorLogger implements ILogger {
@NonNull private final ProgressIndicator myIndicator;
public IndicatorLogger(@NonNull ProgressIndicator indicator) {
myIndicator = indicator;
}
@Override
public void error(@Nullable Throwable t, @Nullable String msgFormat, Object... args) {
if (msgFormat == null && t != null) {
myIndicator.setText2(t.toString());
}
else if (msgFormat != null) {
myIndicator.setText2(String.format(msgFormat, args));
}
}
@Override
public void warning(@NonNull String msgFormat, Object... args) {
myIndicator.setText2(String.format(msgFormat, args));
}
@Override
public void info(@NonNull String msgFormat, Object... args) {
myIndicator.setText2(String.format(msgFormat, args));
}
@Override
public void verbose(@NonNull String msgFormat, Object... args) {
// skip here, don't log verbose strings
}
}
private class LoadTask extends Task.ConditionalModal {
private final List<SdkLoadedCallback> myOnSuccesses = Lists.newArrayList();
private final List<Runnable> myOnErrors = Lists.newArrayList();
private final List<SdkLoadedCallback> myOnLocalCompletes = Lists.newArrayList();
private final boolean myForceRefresh;
private ProgressWindow myProgress;
public LoadTask(boolean canBeCancelled,
@NonNull List<SdkLoadedCallback> onLocalComplete,
@NonNull List<SdkLoadedCallback> onSuccess,
@NonNull List<Runnable> onError,
boolean forceRefresh,
boolean modal) {
super(null /*project*/, "Loading Android SDK", canBeCancelled,
modal ? PerformInBackgroundOption.DEAF : PerformInBackgroundOption.ALWAYS_BACKGROUND);
addCallbacks(onLocalComplete, onSuccess, onError);
myForceRefresh = forceRefresh;
}
public void setProgress(ProgressWindow progress) {
assert myProgress == null;
myProgress = progress;
}
public void addCallbacks(@NonNull List<SdkLoadedCallback> onLocalComplete, @NonNull List<SdkLoadedCallback> onSuccess,
@NonNull List<Runnable> onError) {
myOnLocalCompletes.addAll(onLocalComplete);
myOnSuccesses.addAll(onSuccess);
myOnErrors.addAll(onError);
}
public ProgressWindow getProgress() {
return myProgress;
}
@Override
public void run(@NonNull ProgressIndicator indicator) {
boolean success = false;
try {
IndicatorLogger logger = new IndicatorLogger(indicator);
SdkPackages packages = new SdkPackages();
if (mySdkData != null) {
// fetch local sdk
indicator.setText("Loading local SDK...");
indicator.setText2("");
if (myForceRefresh) {
mySdkData.getLocalSdk().clearLocalPkg(PkgType.PKG_ALL);
}
packages.setLocalPkgInfos(mySdkData.getLocalSdk().getPkgsInfos(PkgType.PKG_ALL));
indicator.setFraction(0.25);
}
if (indicator.isCanceled()) {
return;
}
synchronized (myTaskLock) {
for (SdkLoadedCallback onLocalComplete : myOnLocalCompletes) {
onLocalComplete.run(packages);
}
myOnLocalCompletes.clear();
}
// fetch sdk repository sources.
indicator.setText("Find SDK Repository...");
indicator.setText2("");
SdkSources sources = myRemoteSdk.fetchSources(myForceRefresh ? 0 : RemoteSdk.DEFAULT_EXPIRATION_PERIOD_MS, logger);
indicator.setFraction(0.50);
if (indicator.isCanceled()) {
return;
}
// fetch remote sdk
indicator.setText("Check SDK Repository...");
indicator.setText2("");
Multimap<PkgType, RemotePkgInfo> remotes = myRemoteSdk.fetch(sources, logger);
// compute updates
indicator.setText("Compute SDK updates...");
indicator.setFraction(0.75);
packages.setRemotePkgInfos(remotes);
myPackages = packages;
if (indicator.isCanceled()) {
return;
}
indicator.setText2("");
indicator.setFraction(1.0);
if (indicator.isCanceled()) {
return;
}
success = true;
myLastRefreshMs = System.currentTimeMillis();
}
finally {
myLastRefreshMs = System.currentTimeMillis();
synchronized (myTaskLock) {
// The processing of the task is now complete. To ensure that no more callbacks are added, and to allow another task to be
// kicked off when needed, set myTask to null.
myTask = null;
if (success) {
for (SdkLoadedCallback onLocalComplete : myOnLocalCompletes) { // in case some were added by another call in the interim.
onLocalComplete.run(myPackages);
}
for (SdkLoadedCallback onSuccess : myOnSuccesses) {
onSuccess.run(myPackages);
}
}
else {
for (Runnable onError : myOnErrors) {
onError.run();
}
}
}
}
}
}
}