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