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