blob: d232b75a22bc4125606bb9ce5bf7e4e0a137a09d [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.sdklib.repository.local.LocalPkgInfo;
import com.android.tools.idea.sdk.remote.RemotePkgInfo;
import com.android.tools.idea.sdk.remote.RemoteSdk;
import com.android.tools.idea.sdk.remote.Update;
import com.android.tools.idea.sdk.remote.UpdateResult;
import com.android.tools.idea.sdk.remote.internal.sources.SdkSources;
import com.android.utils.ILogger;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.progress.util.ProgressWindow;
import com.intellij.reference.SoftReference;
import com.intellij.util.concurrency.FutureResult;
import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.android.sdk.AndroidSdkData;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
public class SdkState {
/** Default expiration delay is 24 hours. */
public final static long DEFAULT_EXPIRATION_PERIOD_MS = 24 * 3600 * 1000;
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 LocalPkgInfo[] myLocalPkgInfos = new LocalPkgInfo[0];
private SdkSources mySources;
private UpdateResult myUpdates;
private Multimap<PkgType, RemotePkgInfo> myRemotePkgs;
private long myLastRefreshMs;
private LoadTask myTask;
private final Object myTaskLock = new Object();
private SdkState(@Nullable AndroidSdkData sdkData) {
mySdkData = sdkData;
myRemoteSdk = new RemoteSdk(new LogWrapper(Logger.getInstance(SdkState.class)));
}
@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;
}
// Note: check the cache for actual AndroidSdkData references, not equality.
if (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 LocalPkgInfo[] getLocalPkgInfos() {
return myLocalPkgInfos;
}
public Multimap<PkgType, RemotePkgInfo> getRemotePkgInfos() {
return myRemotePkgs;
}
@Nullable
public UpdateResult getUpdates() {
return myUpdates;
}
public boolean loadAsync(long timeoutMs,
boolean canBeCancelled,
@Nullable Runnable onLocalComplete,
@Nullable Runnable 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<Runnable> onLocalComplete,
@NonNull List<Runnable> onSuccess,
@NonNull List<Runnable> onError,
boolean forceRefresh,
boolean sync) {
if (!forceRefresh && System.currentTimeMillis() - myLastRefreshMs < timeoutMs) {
for (Runnable localComplete : onLocalComplete) {
localComplete.run();
}
for (Runnable success : onSuccess) {
success.run();
}
return false;
}
synchronized (myTaskLock) {
if (myTask != null) {
myTask.addCallbacks(onLocalComplete, onSuccess, onError);
return false;
}
myTask = new LoadTask(canBeCancelled, onLocalComplete, onSuccess, onError, forceRefresh, sync);
}
ProgressWindow progress = new BackgroundableProcessIndicator(myTask);
myTask.setProgress(progress);
ProgressManager.getInstance().run(myTask);
return true;
}
public boolean loadSynchronously(long timeoutMs,
boolean canBeCancelled,
@Nullable Runnable onLocalComplete,
@Nullable Runnable onSuccess,
@Nullable final Runnable onError,
boolean forceRefresh) {
final Semaphore completed = new Semaphore();
completed.down();
Runnable complete = new Runnable() {
@Override
public void run() {
completed.up();
}
};
List<Runnable> onLocalCompletes = createList(onLocalComplete);
List<Runnable> onSuccesses = createList(onSuccess);
List<Runnable> onErrors = createList(onError);
onSuccesses.add(complete);
onErrors.add(complete);
boolean result = load(timeoutMs, canBeCancelled, onLocalCompletes, onSuccesses, onErrors, forceRefresh, true);
ProgressManager pm = ProgressManager.getInstance();
ProgressIndicator indicator = pm.getProgressIndicator();
indicator = indicator == null ? new ProgressWindow(false, false, null) : indicator;
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);
return result;
}
@NonNull
private static List<Runnable> createList(@Nullable Runnable 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<Runnable> myOnSuccesses = Lists.newArrayList();
private final List<Runnable> myOnErrors = Lists.newArrayList();
private final List<Runnable> myOnLocalCompletes = Lists.newArrayList();
private final boolean myForceRefresh;
private ProgressWindow myProgress;
public LoadTask(boolean canBeCancelled,
@NonNull List<Runnable> onLocalComplete,
@NonNull List<Runnable> 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<Runnable> onLocalComplete, @NonNull List<Runnable> 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) {
assert myProgress != null;
boolean success = false;
try {
IndicatorLogger logger = new IndicatorLogger(indicator);
ApplicationEx app = ApplicationManagerEx.getApplicationEx();
SdkLifecycleListener notifier = app.getMessageBus().syncPublisher(SdkLifecycleListener.TOPIC);
if (mySdkData != null) {
// fetch local sdk
indicator.setText("Loading local SDK...");
indicator.setText2("");
if (myForceRefresh) {
mySdkData.getLocalSdk().clearLocalPkg(PkgType.PKG_ALL);
}
myLocalPkgInfos = mySdkData.getLocalSdk().getPkgsInfos(PkgType.PKG_ALL);
notifier.localSdkLoaded(mySdkData);
indicator.setFraction(0.25);
}
if (indicator.isCanceled()) {
return;
}
synchronized (myTaskLock) {
for (Runnable onLocalComplete : myOnLocalCompletes) {
onLocalComplete.run();
}
myOnLocalCompletes.clear();
}
// fetch sdk repository sources.
indicator.setText("Find SDK Repository...");
indicator.setText2("");
mySources = 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("");
myRemotePkgs = myRemoteSdk.fetch(mySources, logger);
notifier.remoteSdkLoaded(mySdkData);
indicator.setFraction(0.75);
if (indicator.isCanceled()) {
return;
}
// compute updates
indicator.setText("Compute SDK updates...");
indicator.setText2("");
myUpdates = Update.computeUpdates(myLocalPkgInfos, myRemotePkgs);
notifier.updatesComputed(mySdkData);
indicator.setFraction(1.0);
if (indicator.isCanceled()) {
return;
}
success = true;
}
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 (Runnable onLocalComplete : myOnLocalCompletes) { // in case some were added by another call in the interim.
onLocalComplete.run();
}
for (Runnable onSuccess : myOnSuccesses) {
onSuccess.run();
}
}
else {
for (Runnable onError : myOnErrors) {
onError.run();
}
}
}
}
}
}
}