• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdkuilib.internal.repository.sdkman2;
18 
19 import com.android.sdklib.internal.repository.Archive;
20 import com.android.sdklib.internal.repository.ITask;
21 import com.android.sdklib.internal.repository.ITaskMonitor;
22 import com.android.sdklib.internal.repository.NullTaskMonitor;
23 import com.android.sdklib.internal.repository.Package;
24 import com.android.sdklib.internal.repository.SdkSource;
25 import com.android.sdklib.internal.repository.Package.UpdateInfo;
26 import com.android.sdkuilib.internal.repository.UpdaterData;
27 
28 import org.eclipse.swt.widgets.Display;
29 import org.eclipse.swt.widgets.Shell;
30 
31 import java.io.File;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 
37 /**
38  * Loads packages fetched from the remote SDK Repository and keeps track
39  * of their state compared with the current local SDK installation.
40  */
41 class PackageLoader {
42 
43     private final UpdaterData mUpdaterData;
44 
45     /**
46      * Interface for the callback called by
47      * {@link PackageLoader#loadPackages(ISourceLoadedCallback)}.
48      * <p/>
49      * After processing each source, the package loader calls {@link #onUpdateSource}
50      * with the list of packages found in that source.
51      * By returning true from {@link #onUpdateSource}, the client tells the loader to
52      * continue and process the next source. By returning false, it tells to stop loading.
53      * <p/>
54      * The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no
55      * matter how the loader stopped, so that the client can clean up or perform any
56      * final action.
57      */
58     public interface ISourceLoadedCallback {
59         /**
60          * After processing each source, the package loader calls this method with the
61          * list of packages found in that source.
62          * By returning true from {@link #onUpdateSource}, the client tells
63          * the loader to continue and process the next source.
64          * By returning false, it tells to stop loading.
65          * <p/>
66          * <em>Important</em>: This method is called from a sub-thread, so clients which
67          * try to access any UI widgets must wrap their calls into
68          * {@link Display#syncExec(Runnable)} or {@link Display#asyncExec(Runnable)}.
69          *
70          * @param packages All the packages loaded from the source. Never null.
71          * @return True if the load operation should continue, false if it should stop.
72          */
onUpdateSource(SdkSource source, Package[] packages)73         public boolean onUpdateSource(SdkSource source, Package[] packages);
74 
75         /**
76          * This method is guaranteed to be called at the end, no matter how the
77          * loader stopped, so that the client can clean up or perform any final action.
78          */
onLoadCompleted()79         public void onLoadCompleted();
80     }
81 
82     /**
83      * Interface describing the task of installing a specific package.
84      * For details on the operation,
85      * see {@link PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)}.
86      *
87      * @see PackageLoader#loadPackagesWithInstallTask(int, IAutoInstallTask)
88      */
89     public interface IAutoInstallTask {
90         /**
91          * Invoked by the loader once a source has been loaded and its package
92          * definitions are known. The method should return the {@code packages}
93          * array and can modify it if necessary.
94          * The loader will call {@link #acceptPackage(Package)} on all the packages returned.
95          *
96          * @param source The source of the packages. Null for the locally installed packages.
97          * @param packages The packages found in the source.
98          */
filterLoadedSource(SdkSource source, Package[] packages)99         public Package[] filterLoadedSource(SdkSource source, Package[] packages);
100 
101         /**
102          * Called by the install task for every package available (new ones, updates as well
103          * as existing ones that don't have a potential update.)
104          * The method should return true if this is a package that should be installed.
105          * <p/>
106          * <em>Important</em>: This method is called from a sub-thread, so clients who try
107          * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)}
108          * or {@link Display#asyncExec(Runnable)}.
109          */
acceptPackage(Package pkg)110         public boolean acceptPackage(Package pkg);
111 
112         /**
113          * Called when the accepted package has been installed, successfully or not.
114          * If an already installed (aka existing) package has been accepted, this will
115          * be called with a 'true' success and the actual install paths.
116          * <p/>
117          * <em>Important</em>: This method is called from a sub-thread, so clients who try
118          * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)}
119          * or {@link Display#asyncExec(Runnable)}.
120          */
setResult(boolean success, Map<Package, File> installPaths)121         public void setResult(boolean success, Map<Package, File> installPaths);
122 
123         /**
124          * Called when the task is done iterating and completed.
125          */
taskCompleted()126         public void taskCompleted();
127     }
128 
129     /**
130      * Creates a new PackageManager associated with the given {@link UpdaterData}.
131      *
132      * @param updaterData The {@link UpdaterData}. Must not be null.
133      */
PackageLoader(UpdaterData updaterData)134     public PackageLoader(UpdaterData updaterData) {
135         mUpdaterData = updaterData;
136     }
137 
138     /**
139      * Loads all packages from the remote repository.
140      * This runs in an {@link ITask}. The call is blocking.
141      * <p/>
142      * The callback is called with each set of {@link PkgItem} found in each source.
143      * The caller is responsible to accumulate the packages given to the callback
144      * after each source is finished loaded. In return the callback tells the loader
145      * whether to continue loading sources.
146      */
loadPackages(final ISourceLoadedCallback sourceLoadedCallback)147     public void loadPackages(final ISourceLoadedCallback sourceLoadedCallback) {
148         try {
149             if (mUpdaterData == null) {
150                 return;
151             }
152 
153             mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
154                 public void run(ITaskMonitor monitor) {
155                     monitor.setProgressMax(10);
156 
157                     // get local packages and offer them to the callback
158                     Package[] localPkgs =
159                         mUpdaterData.getInstalledPackages(monitor.createSubMonitor(1));
160                     if (localPkgs == null) {
161                         localPkgs = new Package[0];
162                     }
163                     if (!sourceLoadedCallback.onUpdateSource(null, localPkgs)) {
164                         return;
165                     }
166 
167                     // get remote packages
168                     boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
169                     mUpdaterData.loadRemoteAddonsList(monitor.createSubMonitor(1));
170 
171                     SdkSource[] sources = mUpdaterData.getSources().getAllSources();
172                     try {
173                         if (sources != null && sources.length > 0) {
174                             ITaskMonitor subMonitor = monitor.createSubMonitor(8);
175                             subMonitor.setProgressMax(sources.length);
176                             for (SdkSource source : sources) {
177                                 Package[] pkgs = source.getPackages();
178                                 if (pkgs == null) {
179                                     source.load(subMonitor.createSubMonitor(1), forceHttp);
180                                     pkgs = source.getPackages();
181                                 }
182                                 if (pkgs == null) {
183                                     continue;
184                                 }
185 
186                                 // Notify the callback a new source has finished loading.
187                                 // If the callback requests so, stop right away.
188                                 if (!sourceLoadedCallback.onUpdateSource(source, pkgs)) {
189                                     return;
190                                 }
191                             }
192                         }
193                     } catch(Exception e) {
194                         monitor.logError("Loading source failed: %1$s", e.toString());
195                     } finally {
196                         monitor.setDescription("Done loading packages.");
197                     }
198                 }
199             });
200         } finally {
201             sourceLoadedCallback.onLoadCompleted();
202         }
203     }
204 
205     /**
206      * Load packages, source by source using {@link #loadPackages(ISourceLoadedCallback)},
207      * and executes the given {@link IAutoInstallTask} on the current package list.
208      * That is for each package known, the install task is queried to find if
209      * the package is the one to be installed or updated.
210      * <p/>
211      * - If an already installed package is accepted by the task, it is returned. <br/>
212      * - If a new package (remotely available but not installed locally) is accepted,
213      * the user will be <em>prompted</em> for permission to install it. <br/>
214      * - If an existing package has updates, the install task will be accept if it
215      * accepts one of the updating packages, and if yes the the user will be
216      * <em>prompted</em> for permission to install it. <br/>
217      * <p/>
218      * Only one package can be accepted, after which the task is completed.
219      * There is no direct return value, {@link IAutoInstallTask#setResult} is called on the
220      * result of the accepted package.
221      * When the task is completed, {@link IAutoInstallTask#taskCompleted()} is called.
222      * <p/>
223      * <em>Important</em>: Since some UI will be displayed to install the selected package,
224      * the {@link UpdaterData} must have a window {@link Shell} associated using
225      * {@link UpdaterData#setWindowShell(Shell)}.
226      * <p/>
227      * The call is blocking. Although the name says "Task", this is not an {@link ITask}
228      * running in its own thread but merely a synchronous call.
229      *
230      * @param installFlags Flags for installation such as
231      *  {@link UpdaterData#TOOLS_MSG_UPDATED_FROM_ADT}.
232      * @param installTask The task to perform.
233      */
loadPackagesWithInstallTask( final int installFlags, final IAutoInstallTask installTask)234     public void loadPackagesWithInstallTask(
235             final int installFlags,
236             final IAutoInstallTask installTask) {
237 
238         loadPackages(new ISourceLoadedCallback() {
239             List<Archive> mArchivesToInstall = new ArrayList<Archive>();
240             Map<Package, File> mInstallPaths = new HashMap<Package, File>();
241 
242             public boolean onUpdateSource(SdkSource source, Package[] packages) {
243                 packages = installTask.filterLoadedSource(source, packages);
244                 if (packages == null || packages.length == 0) {
245                     // Tell loadPackages() to process the next source.
246                     return true;
247                 }
248 
249                 for (Package pkg : packages) {
250                     if (pkg.isLocal()) {
251                         // This is a local (aka installed) package
252                         if (installTask.acceptPackage(pkg)) {
253                             // If the caller is accepting an installed package,
254                             // return a success and give the package's install path
255                             Archive[] a = pkg.getArchives();
256                             // an installed package should have one local compatible archive
257                             if (a.length == 1 && a[0].isCompatible()) {
258                                 mInstallPaths.put(pkg, new File(a[0].getLocalOsPath()));
259                             }
260                         }
261 
262                     } else {
263                         // This is a remote package
264                         if (installTask.acceptPackage(pkg)) {
265                             // The caller is accepting this remote package. We'll install it.
266                             for (Archive archive : pkg.getArchives()) {
267                                 if (archive.isCompatible()) {
268                                     mArchivesToInstall.add(archive);
269                                     break;
270                                 }
271                             }
272                         }
273                     }
274                 }
275 
276                 // Tell loadPackages() to process the next source.
277                 return true;
278             }
279 
280             public void onLoadCompleted() {
281                 if (!mArchivesToInstall.isEmpty()) {
282                     installArchives(mArchivesToInstall);
283                 }
284                 if (mInstallPaths == null) {
285                     installTask.setResult(false, null);
286                 } else {
287                     installTask.setResult(true, mInstallPaths);
288                 }
289 
290                 installTask.taskCompleted();
291             }
292 
293             /**
294              * Shows the UI of the install selector.
295              * If the package is then actually installed, refresh the local list and
296              * notify the install task of the installation path.
297              *
298              * @param archivesToInstall The archives to install.
299              */
300             private void installArchives(final List<Archive> archivesToInstall) {
301                 // Actually install the new archives that we just found.
302                 // This will display some UI so we need a shell's sync exec.
303 
304                 final List<Archive> installedArchives = new ArrayList<Archive>();
305 
306                 Shell shell = mUpdaterData.getWindowShell();
307                 if (shell != null && !shell.isDisposed()) {
308                     shell.getDisplay().syncExec(new Runnable() {;
309                         public void run() {
310                             List<Archive> archives =
311                                 mUpdaterData.updateOrInstallAll_WithGUI(
312                                     archivesToInstall,
313                                     true /* includeObsoletes */,
314                                     installFlags);
315 
316                             if (archives != null) {
317                                 installedArchives.addAll(archives);
318                             }
319                         }
320                     });
321                 }
322 
323                 if (installedArchives.isEmpty()) {
324                     // We failed to install anything.
325                     mInstallPaths = null;
326                     return;
327                 }
328 
329                 // The local package list has changed, make sure to refresh it
330                 mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
331                 mUpdaterData.getLocalSdkParser().clearPackages();
332                 final Package[] localPkgs = mUpdaterData.getInstalledPackages(
333                         new NullTaskMonitor(mUpdaterData.getSdkLog()));
334 
335                 // Scan the installed package list to find the install paths.
336                 for (Archive installedArchive : installedArchives) {
337                     Package pkg = installedArchive.getParentPackage();
338 
339                     for (Package localPkg : localPkgs) {
340                         if (localPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
341                             Archive[] localArchive = localPkg.getArchives();
342                             if (localArchive.length == 1 && localArchive[0].isCompatible()) {
343                                 mInstallPaths.put(
344                                     localPkg,
345                                     new File(localArchive[0].getLocalOsPath()));
346                             }
347                         }
348                     }
349                 }
350             }
351         });
352     }
353 }
354