1 /* 2 * Copyright (C) 2009 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.sdkman1; 18 19 import com.android.sdklib.internal.repository.Archive; 20 import com.android.sdklib.internal.repository.IDescription; 21 import com.android.sdklib.internal.repository.ITask; 22 import com.android.sdklib.internal.repository.ITaskMonitor; 23 import com.android.sdklib.internal.repository.NullTaskMonitor; 24 import com.android.sdklib.internal.repository.Package; 25 import com.android.sdklib.internal.repository.SdkSource; 26 import com.android.sdklib.internal.repository.SdkSourceCategory; 27 import com.android.sdklib.internal.repository.Package.UpdateInfo; 28 import com.android.sdkuilib.internal.repository.UpdaterData; 29 import com.android.sdkuilib.internal.repository.icons.ImageFactory; 30 31 import org.eclipse.jface.viewers.IContentProvider; 32 import org.eclipse.jface.viewers.ILabelProvider; 33 import org.eclipse.jface.viewers.ITreeContentProvider; 34 import org.eclipse.jface.viewers.LabelProvider; 35 import org.eclipse.jface.viewers.Viewer; 36 import org.eclipse.swt.graphics.Image; 37 38 import java.util.ArrayList; 39 40 /** 41 * A list of sdk-repository sources. 42 * 43 * This implementation is UI dependent. 44 */ 45 public class RepoSourcesAdapter { 46 47 private final UpdaterData mUpdaterData; 48 49 /** 50 * A dummy RepoSource entry returned for sources which had load errors. 51 * It displays a summary of the error as its short description or 52 * it displays the source's long description. 53 */ 54 public static class RepoSourceError implements IDescription { 55 56 private final SdkSource mSource; 57 58 public RepoSourceError(SdkSource source) { 59 mSource = source; 60 } 61 62 public String getLongDescription() { 63 return mSource.getLongDescription(); 64 } 65 66 public String getShortDescription() { 67 return mSource.getFetchError(); 68 } 69 } 70 71 /** 72 * A dummy RepoSource entry returned for sources with no packages. 73 * We need that to force the SWT tree to display an open/close triangle 74 * even for empty sources. 75 */ 76 public static class RepoSourceEmpty implements IDescription { 77 78 private final SdkSource mSource; 79 private final boolean mEmptyBecauseOfUpdateOnly; 80 81 public RepoSourceEmpty(SdkSource source, boolean emptyBecauseOfUpdateOnly) { 82 mSource = source; 83 mEmptyBecauseOfUpdateOnly = emptyBecauseOfUpdateOnly; 84 } 85 86 public String getLongDescription() { 87 return mSource.getLongDescription(); 88 } 89 90 public String getShortDescription() { 91 if (mEmptyBecauseOfUpdateOnly) { 92 return "Some packages were found but are not compatible updates."; 93 } else { 94 return "No packages found"; 95 } 96 } 97 } 98 99 public RepoSourcesAdapter(UpdaterData updaterData) { 100 mUpdaterData = updaterData; 101 } 102 103 public ILabelProvider getLabelProvider() { 104 return new ViewerLabelProvider(); 105 } 106 107 108 public IContentProvider getContentProvider() { 109 return new TreeContentProvider(); 110 } 111 112 // ------------ 113 114 private class ViewerLabelProvider extends LabelProvider { 115 116 /** Returns an image appropriate for this element. */ 117 @Override 118 public Image getImage(Object element) { 119 120 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 121 122 if (imgFactory != null) { 123 return imgFactory.getImageForObject(element); 124 } 125 126 return super.getImage(element); 127 } 128 129 /** Returns the toString of the element. */ 130 @Override 131 public String getText(Object element) { 132 if (element instanceof IDescription) { 133 return ((IDescription) element).getShortDescription(); 134 } 135 return super.getText(element); 136 } 137 } 138 139 // ------------ 140 141 private class TreeContentProvider implements ITreeContentProvider { 142 143 // Called when the viewer is disposed 144 public void dispose() { 145 // pass 146 } 147 148 // Called when the input is set or changed on the provider 149 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 150 // pass 151 } 152 153 /** 154 * Called to collect the root elements for the given input. 155 * The input here is a {@link RepoSourcesAdapter} object, this returns an array 156 * of {@link SdkSource}. 157 */ 158 public Object[] getElements(Object inputElement) { 159 return getChildren(inputElement); 160 } 161 162 /** 163 * Get the children of the given parent. This is requested on-demand as 164 * nodes are expanded. 165 * 166 * For a {@link RepoSourcesAdapter} object, returns an array of {@link SdkSource}s. 167 * For a {@link SdkSource}, returns an array of {@link Package}s. 168 * For a {@link Package}, returns an array of {@link Archive}s. 169 */ 170 public Object[] getChildren(Object parentElement) { 171 if (parentElement == RepoSourcesAdapter.this) { 172 return mUpdaterData.getSources().getCategories(); 173 174 } else if (parentElement instanceof SdkSourceCategory) { 175 SdkSourceCategory cat = (SdkSourceCategory) parentElement; 176 if (cat == SdkSourceCategory.ADDONS_3RD_PARTY) { 177 mUpdaterData.loadRemoteAddonsList( 178 new NullTaskMonitor(mUpdaterData.getSdkLog())); 179 } 180 181 SdkSource[] sources = mUpdaterData.getSources().getSources(cat); 182 183 if (sources.length == 1 && sources[0] != null) { 184 // If a source has a single element and this element has the same 185 // uiName as the category, collapse both. 186 // Basically this is a kludge so that we don't end up with 187 // Android Repository > Android Repository 188 // at the top level. 189 if (cat.getUiName().equals(sources[0].getUiName())) { 190 return getRepoSourceChildren(sources[0]); 191 } 192 } 193 194 return sources; 195 196 } else if (parentElement instanceof SdkSource) { 197 return getRepoSourceChildren((SdkSource) parentElement); 198 199 } else if (parentElement instanceof Package) { 200 return getPackageChildren((Package) parentElement); 201 } 202 203 return new Object[0]; 204 } 205 206 /** 207 * Returns the list of packages for this repo source, eventually filtered to display 208 * only update packages. If the list is empty, returns a specific empty node. If there's 209 * an error, returns a specific error node. 210 */ 211 private Object[] getRepoSourceChildren(final SdkSource source) { 212 Package[] packages = source.getPackages(); 213 214 if (packages == null && source.getFetchError() == null) { 215 final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); 216 217 mUpdaterData.getTaskFactory().start("Loading Source", new ITask() { 218 public void run(ITaskMonitor monitor) { 219 source.load(monitor, forceHttp); 220 } 221 }); 222 223 packages = source.getPackages(); 224 } 225 226 boolean wasEmptyBeforeFilter = (packages == null || packages.length == 0); 227 228 // filter out only the packages that are new/upgrade. 229 if (packages != null && mUpdaterData.getSettingsController().getShowUpdateOnly()) { 230 packages = filterUpdateOnlyPackages(packages); 231 } 232 if (packages != null && packages.length == 0) { 233 packages = null; 234 } 235 236 ArrayList<Object> results = new ArrayList<Object>(); 237 238 if (source.getFetchError() != null) { 239 // Insert a dummy entry to display the fetch error 240 results.add(new RepoSourceError(source)); 241 } 242 243 // Either return a non-null package list or create a new empty node 244 if (packages != null) { 245 for (Package p : packages) { 246 results.add(p); 247 } 248 } else { 249 results.add(new RepoSourceEmpty(source, !wasEmptyBeforeFilter)); 250 } 251 252 return results.toArray(); 253 } 254 255 /** 256 * Returns the list of archives for the given package, eventually filtering it 257 * to only show the compatible archives. 258 */ 259 private Object[] getPackageChildren(Package pkg) { 260 Archive[] archives = pkg.getArchives(); 261 if (mUpdaterData.getSettingsController().getShowUpdateOnly()) { 262 for (Archive archive : archives) { 263 264 // if we only want the compatible archives, then we just take the first 265 // one. it's unlikely there are 2 compatible archives for the same 266 // package 267 if (archive.isCompatible()) { 268 return new Object[] { archive }; 269 } 270 } 271 } 272 273 return archives; 274 } 275 276 /** 277 * Returns the parent of a given element. 278 * The input {@link RepoSourcesAdapter} is the parent of all {@link SdkSource} elements. 279 */ 280 public Object getParent(Object element) { 281 282 if (element instanceof SdkSourceCategory) { 283 return RepoSourcesAdapter.this; 284 285 } else if (element instanceof SdkSource) { 286 return mUpdaterData.getSources().getCategory((SdkSource) element); 287 288 } else if (element instanceof Package) { 289 return ((Package) element).getParentSource(); 290 291 } else if (element instanceof Archive) { 292 return ((Archive) element).getParentPackage(); 293 } 294 return null; 295 } 296 297 /** 298 * Returns true if a given element has children, which is used to display a 299 * "+/expand" box next to the tree node. 300 * All non-terminal elements are expandable, whether they actually have any children 301 * or not. This is necessary on windows in order for the tree to display the "triangle" 302 * icon so that users can actually expand it, which will fill the node at runtime. 303 */ 304 public boolean hasChildren(Object element) { 305 return element instanceof SdkSourceCategory || 306 element instanceof SdkSource || 307 element instanceof Package; 308 } 309 } 310 311 /** 312 * Filters out a list of remote packages to only keep the ones that are either new or 313 * updates of existing package. This also removes obsolete packages. 314 * 315 * @param remotePackages the list of packages to filter. 316 * @return a non null (but maybe empty) list of new or update packages. 317 */ 318 private Package[] filterUpdateOnlyPackages(Package[] remotePackages) { 319 // get the installed packages 320 Package[] installedPackages = mUpdaterData.getInstalledPackages( 321 new NullTaskMonitor(mUpdaterData.getSdkLog())); 322 323 // we'll populate this package list with either upgrades or new packages. 324 ArrayList<Package> filteredList = new ArrayList<Package>(); 325 326 // for each remote packages, we look for an existing version. 327 // If no existing version -> add to the list 328 // if existing version but with older revision -> add it to the list 329 for (Package remotePkg : remotePackages) { 330 331 // Obsolete packages are not offered as updates. 332 if (remotePkg.isObsolete()) { 333 continue; 334 } 335 336 // For all potential packages, we also make sure that there's an archive for 337 // the current platform, or we simply skip them. 338 if (remotePkg.hasCompatibleArchive()) { 339 boolean keepPkg = true; 340 341 nextPkg: for (Package installedPkg : installedPackages) { 342 UpdateInfo info = installedPkg.canBeUpdatedBy(remotePkg); 343 switch(info) { 344 case UPDATE: 345 // The remote package is an update to an existing one. 346 // We're done looking. 347 keepPkg = true; 348 break nextPkg; 349 case NOT_UPDATE: 350 // The remote package is the same as one that is already installed. 351 keepPkg = false; 352 break; 353 case INCOMPATIBLE: 354 // We can't compare and decide on incompatible things. 355 break; 356 } 357 } 358 359 if (keepPkg) { 360 filteredList.add(remotePkg); 361 } 362 } 363 } 364 365 return filteredList.toArray(new Package[filteredList.size()]); 366 } 367 } 368