OSDN Git Service

6e2995e0c25cc14ad8e018bda49b0ab1299faa62
[android-x86/sdk.git] / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / repository / PackageLoader.java
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;
18
19 import com.android.sdklib.internal.repository.Archive;
20 import com.android.sdklib.internal.repository.IPackageVersion;
21 import com.android.sdklib.internal.repository.ITask;
22 import com.android.sdklib.internal.repository.ITaskMonitor;
23 import com.android.sdklib.internal.repository.LocalSdkParser;
24 import com.android.sdklib.internal.repository.Package;
25 import com.android.sdklib.internal.repository.SdkSource;
26 import com.android.sdklib.internal.repository.Package.UpdateInfo;
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.List;
34
35 /**
36  * Loads packages fetched from the remote SDK Repository and keeps track
37  * of their state compared with the current local SDK installation.
38  */
39 class PackageLoader {
40
41     private final UpdaterData mUpdaterData;
42
43     /**
44      * Interface for the callback called by
45      * {@link PackageLoader#loadPackages(ISourceLoadedCallback)}.
46      * <p/>
47      * After processing each source, the package loader calls {@link #onSourceLoaded(List)}
48      * with the list of package items found in that source. The client should process that
49      * list as it want, typically by accumulating the package items in a list of its own.
50      * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to
51      * continue and process the next source. By returning false, it tells to stop loading.
52      * <p/>
53      * The {@link #onLoadCompleted()} method is guaranteed to be called at the end, no
54      * matter how the loader stopped, so that the client can clean up or perform any
55      * final action.
56      */
57     public interface ISourceLoadedCallback {
58         /**
59          * After processing each source, the package loader calls this method with the
60          * list of package items found in that source. The client should process that
61          * list as it want, typically by accumulating the package items in a list of its own.
62          * By returning true from {@link #onSourceLoaded(List)}, the client tells the loader to
63          * continue and process the next source. By returning false, it tells to stop loading.
64          * <p/>
65          * <em>Important</em>: This method is called from a sub-thread, so clients who try
66          * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)}
67          * or {@link Display#asyncExec(Runnable)}.
68          *
69          * @param pkgItems All the package items loaded from the last processed source.
70          *  This is a copy and the client can hold to this list or modify it in any way.
71          * @return True if the load operation should continue, false if it should stop.
72          */
73         public boolean onSourceLoaded(List<PkgItem> pkgItems);
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          */
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(IAutoInstallTask)}.
86      *
87      * @see PackageLoader#loadPackagesWithInstallTask(IAutoInstallTask)
88      */
89     public interface IAutoInstallTask {
90         /**
91          * Called by the install task for every package available (new ones, updates as well
92          * as existing ones that don't have a potential update.)
93          * The method should return true if this is the package that should be installed,
94          * at which point the packager loader will stop processing the next packages and sources.
95          * <p/>
96          * <em>Important</em>: This method is called from a sub-thread, so clients who try
97          * to access any UI widgets must wrap their calls into {@link Display#syncExec(Runnable)}
98          * or {@link Display#asyncExec(Runnable)}.
99          */
100         public boolean acceptPackage(Package pkg);
101
102         /**
103          * Called when the accepted package has been installed, successfully or not.
104          * If an already installed (aka existing) package has been accepted, this will
105          * be called with a 'true' success and the actual install path.
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          */
111         public void setResult(Package pkg, boolean success, File installPath);
112
113         /**
114          * Called when the task is done iterating and completed.
115          */
116         public void taskCompleted();
117     }
118
119     /**
120      * Creates a new PackageManager associated with the given {@link UpdaterData}.
121      *
122      * @param updaterData The {@link UpdaterData}. Must not be null.
123      */
124     public PackageLoader(UpdaterData updaterData) {
125         mUpdaterData = updaterData;
126     }
127
128     /**
129      * Loads all packages from the remote repository.
130      * This runs in an {@link ITask}. The call is blocking.
131      * <p/>
132      * The callback is called with each set of {@link PkgItem} found in each source.
133      * The caller is responsible to accumulate the packages given to the callback
134      * after each source is finished loaded. In return the callback tells the loader
135      * whether to continue loading sources.
136      */
137     public void loadPackages(final ISourceLoadedCallback sourceLoadedCallback) {
138         try {
139             if (mUpdaterData == null) {
140                 return;
141             }
142
143             // get local packages and offer them to the callback
144             List<PkgItem> localPkgItems =  loadLocalPackages();
145             if (!localPkgItems.isEmpty()) {
146                 if (!sourceLoadedCallback.onSourceLoaded(localPkgItems)) {
147                     return;
148                 }
149             }
150
151             final int[] numPackages = { localPkgItems.size() };
152
153             // get remote packages
154             final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp();
155             mUpdaterData.loadRemoteAddonsList();
156             mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() {
157                 public void run(ITaskMonitor monitor) {
158                     SdkSource[] sources = mUpdaterData.getSources().getAllSources();
159                     try {
160                         for (SdkSource source : sources) {
161                             Package[] pkgs = source.getPackages();
162                             if (pkgs == null) {
163                                 source.load(monitor, forceHttp);
164                                 pkgs = source.getPackages();
165                             }
166                             if (pkgs == null) {
167                                 continue;
168                             }
169
170                             List<PkgItem> sourcePkgItems = new ArrayList<PkgItem>();
171                             for(Package pkg : pkgs) {
172                                 PkgItem pi = new PkgItem(pkg, PkgState.NEW);
173                                 sourcePkgItems.add(pi);
174                             }
175
176                             numPackages[0] += sourcePkgItems.size();
177
178                             // Notify the callback a new source has finished loading.
179                             // If the callback requests so, stop right away.
180                             if (!sourceLoadedCallback.onSourceLoaded(sourcePkgItems)) {
181                                 return;
182                             }
183                         }
184                     } catch(Exception e) {
185                         monitor.logError("Loading source failed: %1$s", e.toString());
186                     } finally {
187                         monitor.setDescription("Done loading %1$d packages from %2$d sources",
188                                 numPackages[0],
189                                 sources.length);
190                     }
191                 }
192             });
193         } finally {
194             sourceLoadedCallback.onLoadCompleted();
195         }
196     }
197
198     /**
199      * Internal method that returns all installed packages from the {@link LocalSdkParser}
200      * associated with the {@link UpdaterData}.
201      * <p/>
202      * Note that the {@link LocalSdkParser} maintains a cache, so callers need to clear
203      * it if they know they changed the local installation.
204      *
205      * @return A new list of {@link PkgItem}. May be empty but never null.
206      */
207     private List<PkgItem> loadLocalPackages() {
208         List<PkgItem> pkgItems = new ArrayList<PkgItem>();
209
210         for (Package pkg : mUpdaterData.getInstalledPackages()) {
211             PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED);
212             pkgItems.add(pi);
213         }
214
215         return pkgItems;
216     }
217
218     /**
219      * Load packages, source by source using {@link #loadPackages(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 installTask The task to perform.
244      */
245     public void loadPackagesWithInstallTask(final IAutoInstallTask installTask) {
246
247         loadPackages(new ISourceLoadedCallback() {
248             public boolean onSourceLoaded(List<PkgItem> pkgItems) {
249                 for (PkgItem item : pkgItems) {
250                     Package acceptedPkg = null;
251                     switch(item.getState()) {
252                     case NEW:
253                         if (installTask.acceptPackage(item.getMainPackage())) {
254                             acceptedPkg = item.getMainPackage();
255                         }
256                         if (item.hasUpdatePkg() && installTask.acceptPackage(item.getUpdatePkg())) {
257                             acceptedPkg = item.getUpdatePkg();
258                         }
259                         break;
260                     case INSTALLED:
261                         if (installTask.acceptPackage(item.getMainPackage())) {
262                             // If the caller is accepting an installed package,
263                             // return a success and give the package's install path
264                             acceptedPkg = item.getMainPackage();
265                             Archive[] a = acceptedPkg.getArchives();
266                             // an installed package should have one local compatible archive
267                             if (a.length == 1 && a[0].isCompatible()) {
268                                 installTask.setResult(
269                                         acceptedPkg,
270                                         true /*success*/,
271                                         new File(a[0].getLocalOsPath()));
272
273                                 // return false to tell loadPackages() that we don't
274                                 // need to continue processing any more sources.
275                                 return false;
276                             }
277                         }
278                     }
279
280                     if (acceptedPkg != null) {
281                         // Try to install this package if it has one compatible archive.
282                         Archive archiveToInstall = null;
283
284                         for (Archive a2 : acceptedPkg.getArchives()) {
285                             if (a2.isCompatible()) {
286                                 archiveToInstall = a2;
287                                 break;
288                             }
289                         }
290
291                         if (archiveToInstall != null) {
292                             installArchive(archiveToInstall);
293                         }
294
295                         // return false to tell loadPackages() that we don't
296                         // need to continue processing any more sources.
297                         return false;
298                     }
299
300                 }
301                 // Tell loadPackages() to process the next source.
302                 return true;
303             }
304
305             /**
306              * Shows the UI of the install selector.
307              * If the package is then actually installed, refresh the local list and
308              * notify the install task of the installation path.
309              *
310              * @param archiveToInstall The archive to install.
311              */
312             private void installArchive(Archive archiveToInstall) {
313                 // What we want to install
314                 final ArrayList<Archive> archivesToInstall = new ArrayList<Archive>();
315                 archivesToInstall.add(archiveToInstall);
316
317                 Package packageToInstall = archiveToInstall.getParentPackage();
318
319                 // What we'll end up installing
320                 final Archive[] installedArchive = new Archive[] { null };
321
322                 // Actually install the new archive that we just found.
323                 // This will display some UI so we need a shell's sync exec.
324
325                 mUpdaterData.getWindowShell().getDisplay().syncExec(new Runnable() {
326                     public void run() {
327                         List<Archive> archives =
328                             mUpdaterData.updateOrInstallAll_WithGUI(
329                                 archivesToInstall,
330                                 true /* includeObsoletes */);
331
332                         if (archives != null && !archives.isEmpty()) {
333                             // We expect that at most one archive has been installed.
334                             assert archives.size() == 1;
335                             installedArchive[0] = archives.get(0);
336                         }
337                     }
338                 });
339
340                 // If the desired package has been installed...
341                 if (installedArchive[0] == archiveToInstall) {
342
343                     // The local package list has changed, make sure to refresh it
344                     mUpdaterData.getLocalSdkParser().clearPackages();
345                     final List<PkgItem> localPkgItems = loadLocalPackages();
346
347                     // Try to locate the installed package in the new package list
348                     for (PkgItem localItem : localPkgItems) {
349                         Package localPkg = localItem.getMainPackage();
350                         if (localPkg.canBeUpdatedBy(packageToInstall) == UpdateInfo.NOT_UPDATE) {
351                             Archive[] localArchive = localPkg.getArchives();
352                             if (localArchive.length == 1 && localArchive[0].isCompatible()) {
353                                 installTask.setResult(
354                                         localPkg,
355                                         true /*success*/,
356                                         new File(localArchive[0].getLocalOsPath()));
357                                 return;
358                             }
359                         }
360                     }
361                 }
362
363                 // We failed to install the package.
364                 installTask.setResult(packageToInstall, false /*success*/, null);
365             }
366
367             public void onLoadCompleted() {
368                 installTask.taskCompleted();
369             }
370         });
371
372     }
373
374     /**
375      * The state of the a given {@link PkgItem}, that is the relationship between
376      * a given remote package and the local repository.
377      */
378     public enum PkgState {
379         /**
380          * Package is locally installed and may or may not have an update.
381          */
382         INSTALLED,
383
384         /**
385          * There's a new package available on the remote site that isn't installed locally.
386          */
387         NEW
388     }
389
390     /**
391      * A {@link PkgItem} represents one main {@link Package} combined with its state
392      * and an optional update package.
393      * <p/>
394      * The main package is final and cannot change since it's what "defines" this PkgItem.
395      * The state or update package can change later.
396      */
397     public static class PkgItem implements Comparable<PkgItem> {
398         private PkgState mState;
399         private final Package mMainPkg;
400         private Package mUpdatePkg;
401
402         /**
403          * Create a new {@link PkgItem} for this main package.
404          * The main package is final and cannot change since it's what "defines" this PkgItem.
405          * The state or update package can change later.
406          */
407         public PkgItem(Package mainPkg, PkgState state) {
408             mMainPkg = mainPkg;
409             mState = state;
410             assert mMainPkg != null;
411         }
412
413         public boolean isObsolete() {
414             return mMainPkg.isObsolete();
415         }
416
417         public Package getUpdatePkg() {
418             return mUpdatePkg;
419         }
420
421         public boolean hasUpdatePkg() {
422             return mState == PkgState.INSTALLED && mUpdatePkg != null;
423         }
424
425         public String getName() {
426             return mMainPkg.getListDescription();
427         }
428
429         public int getRevision() {
430             return mMainPkg.getRevision();
431         }
432
433         public String getDescription() {
434             return mMainPkg.getDescription();
435         }
436
437         public Package getMainPackage() {
438             return mMainPkg;
439         }
440
441         public PkgState getState() {
442             return mState;
443         }
444
445         public SdkSource getSource() {
446             return mMainPkg.getParentSource();
447         }
448
449         public int getApi() {
450             return mMainPkg instanceof IPackageVersion ?
451                     ((IPackageVersion) mMainPkg).getVersion().getApiLevel() :
452                         -1;
453         }
454
455         public Archive[] getArchives() {
456             return mMainPkg.getArchives();
457         }
458
459         public int compareTo(PkgItem pkg) {
460             return getMainPackage().compareTo(pkg.getMainPackage());
461         }
462
463         /**
464          * Returns true if this package or its updating packages contains
465          * the exact given archive.
466          * Important: This compares object references, not object equality.
467          */
468         public boolean hasArchive(Archive archive) {
469             if (mMainPkg.hasArchive(archive)) {
470                 return true;
471             }
472             if (mUpdatePkg != null && mUpdatePkg.hasArchive(archive)) {
473                 return true;
474             }
475             return false;
476         }
477
478         /**
479          * Checks whether the main packages are of the same type and are
480          * not an update of each other.
481          */
482         public boolean isSameMainPackageAs(Package pkg) {
483             if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE) {
484                 // package revision numbers must match
485                 return mMainPkg.getRevision() == pkg.getRevision();
486             }
487             return false;
488         }
489
490         /**
491          * Checks whether too {@link PkgItem} are the same.
492          * This checks both items have the same state, both main package are similar
493          * and that they have the same updating packages.
494          */
495         public boolean isSameItemAs(PkgItem item) {
496             if (this == item) {
497                 return true;
498             }
499             boolean same = this.mState == item.mState;
500             if (same) {
501                 same = isSameMainPackageAs(item.getMainPackage());
502             }
503
504             if (same) {
505                 // check updating packages are the same
506                 Package p1 = this.mUpdatePkg;
507                 Package p2 = item.getUpdatePkg();
508                 same = (p1 == p2) || (p1 == null && p2 == null) || (p1 != null && p2 != null);
509
510                 if (same && p1 != null) {
511                     same = p1.canBeUpdatedBy(p2) == UpdateInfo.NOT_UPDATE;
512                 }
513             }
514
515             return same;
516         }
517
518         /**
519          * Equality is defined as {@link #isSameItemAs(PkgItem)}: state, main package
520          * and update package must be the similar.
521          */
522         @Override
523         public boolean equals(Object obj) {
524             return (obj instanceof PkgItem) && this.isSameItemAs((PkgItem) obj);
525         }
526
527         @Override
528         public int hashCode() {
529             final int prime = 31;
530             int result = 1;
531             result = prime * result + ((mState     == null) ? 0 : mState.hashCode());
532             result = prime * result + ((mMainPkg   == null) ? 0 : mMainPkg.hashCode());
533             result = prime * result + ((mUpdatePkg == null) ? 0 : mUpdatePkg.hashCode());
534             return result;
535         }
536
537         /**
538          * Check whether the 'pkg' argument is an update for this package.
539          * If it is, record it as an updating package.
540          * If there's already an updating package, only keep the most recent update.
541          * Returns true if it is update (even if there was already an update and this
542          * ended up not being the most recent), false if incompatible or not an update.
543          *
544          * This should only be used for installed packages.
545          */
546         public boolean mergeUpdate(Package pkg) {
547             if (mUpdatePkg == pkg) {
548                 return true;
549             }
550             if (mMainPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
551                 if (mUpdatePkg == null) {
552                     mUpdatePkg = pkg;
553                 } else if (mUpdatePkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) {
554                     // If we have more than one, keep only the most recent update
555                     mUpdatePkg = pkg;
556                 }
557                 return true;
558             }
559
560             return false;
561         }
562
563         public void removeUpdate() {
564             mUpdatePkg = null;
565         }
566
567         /** Returns a string representation of this item, useful when debugging. */
568         @Override
569         public String toString() {
570             StringBuilder sb = new StringBuilder();
571             sb.append('<');
572             sb.append(mState.toString());
573
574             if (mMainPkg != null) {
575                 sb.append(", pkg:"); //$NON-NLS-1$
576                 sb.append(mMainPkg.toString());
577             }
578
579             if (mUpdatePkg != null) {
580                 sb.append(", updated by:"); //$NON-NLS-1$
581                 sb.append(mUpdatePkg.toString());
582             }
583
584             sb.append('>');
585             return sb.toString();
586         }
587
588     }
589 }