| /* |
| * 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(); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |