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