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.sdklib.internal.repository; 18 19 import com.android.prefs.AndroidLocation; 20 import com.android.prefs.AndroidLocation.AndroidLocationException; 21 import com.android.sdklib.ISdkLog; 22 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.util.ArrayList; 28 import java.util.EnumMap; 29 import java.util.Iterator; 30 import java.util.Properties; 31 import java.util.Map.Entry; 32 33 /** 34 * A list of sdk-repository and sdk-addon sources, sorted by {@link SdkSourceCategory}. 35 */ 36 public class SdkSources { 37 38 private static final String KEY_COUNT = "count"; 39 40 private static final String KEY_SRC = "src"; 41 42 private static final String SRC_FILENAME = "repositories.cfg"; //$NON-NLS-1$ 43 44 private final EnumMap<SdkSourceCategory, ArrayList<SdkSource>> mSources = 45 new EnumMap<SdkSourceCategory, ArrayList<SdkSource>>(SdkSourceCategory.class); 46 47 public SdkSources() { 48 } 49 50 /** 51 * Adds a new source to the Sources list. 52 */ 53 public void add(SdkSourceCategory category, SdkSource source) { 54 synchronized (mSources) { 55 ArrayList<SdkSource> list = mSources.get(category); 56 if (list == null) { 57 list = new ArrayList<SdkSource>(); 58 mSources.put(category, list); 59 } 60 61 list.add(source); 62 } 63 } 64 65 /** 66 * Removes a source from the Sources list. 67 */ 68 public void remove(SdkSource source) { 69 synchronized (mSources) { 70 Iterator<Entry<SdkSourceCategory, ArrayList<SdkSource>>> it = 71 mSources.entrySet().iterator(); 72 while (it.hasNext()) { 73 Entry<SdkSourceCategory, ArrayList<SdkSource>> entry = it.next(); 74 ArrayList<SdkSource> list = entry.getValue(); 75 76 if (list.remove(source)) { 77 if (list.isEmpty()) { 78 // remove the entry since the source list became empty 79 it.remove(); 80 } 81 } 82 } 83 } 84 } 85 86 /** 87 * Removes all the sources in the given category. 88 */ 89 public void removeAll(SdkSourceCategory category) { 90 synchronized (mSources) { 91 mSources.remove(category); 92 } 93 } 94 95 /** 96 * Returns a set of all categories that must be displayed. This includes all 97 * categories that are to be always displayed as well as all categories which 98 * have at least one source. 99 * Might return a empty array, but never returns null. 100 */ 101 public SdkSourceCategory[] getCategories() { 102 ArrayList<SdkSourceCategory> cats = new ArrayList<SdkSourceCategory>(); 103 104 for (SdkSourceCategory cat : SdkSourceCategory.values()) { 105 if (cat.getAlwaysDisplay()) { 106 cats.add(cat); 107 } else { 108 synchronized (mSources) { 109 ArrayList<SdkSource> list = mSources.get(cat); 110 if (list != null && !list.isEmpty()) { 111 cats.add(cat); 112 } 113 } 114 } 115 } 116 117 return cats.toArray(new SdkSourceCategory[cats.size()]); 118 } 119 120 /** 121 * Returns a new array of sources attached to the given category. 122 * Might return an empty array, but never returns null. 123 */ 124 public SdkSource[] getSources(SdkSourceCategory category) { 125 synchronized (mSources) { 126 ArrayList<SdkSource> list = mSources.get(category); 127 if (list == null) { 128 return new SdkSource[0]; 129 } else { 130 return list.toArray(new SdkSource[list.size()]); 131 } 132 } 133 } 134 135 /** 136 * Returns an array of the sources across all categories. This is never null. 137 */ 138 public SdkSource[] getAllSources() { 139 synchronized (mSources) { 140 int n = 0; 141 142 for (ArrayList<SdkSource> list : mSources.values()) { 143 n += list.size(); 144 } 145 146 SdkSource[] sources = new SdkSource[n]; 147 148 int i = 0; 149 for (ArrayList<SdkSource> list : mSources.values()) { 150 for (SdkSource source : list) { 151 sources[i++] = source; 152 } 153 } 154 155 return sources; 156 } 157 } 158 159 /** 160 * Each source keeps a local cache of whatever it loaded recently. 161 * This calls {@link SdkSource#clearPackages()} on all the available sources, 162 * and the next call to {@link SdkSource#getPackages()} will actually reload 163 * the remote package list. 164 */ 165 public void clearAllPackages() { 166 synchronized (mSources) { 167 for (ArrayList<SdkSource> list : mSources.values()) { 168 for (SdkSource source : list) { 169 source.clearPackages(); 170 } 171 } 172 } 173 } 174 175 /** 176 * Returns the category of a given source, or null if the source is unknown. 177 * <p/> 178 * Note that this method uses object identity to find a given source, and does 179 * not identify sources by their URL like {@link #hasSourceUrl(SdkSource)} does. 180 * <p/> 181 * The search is O(N), which should be acceptable on the expectedly small source list. 182 */ 183 public SdkSourceCategory getCategory(SdkSource source) { 184 if (source != null) { 185 synchronized (mSources) { 186 for (Entry<SdkSourceCategory, ArrayList<SdkSource>> entry : mSources.entrySet()) { 187 if (entry.getValue().contains(source)) { 188 return entry.getKey(); 189 } 190 } 191 } 192 } 193 return null; 194 } 195 196 /** 197 * Returns true if there's already a similar source in the sources list 198 * under any category. 199 * <p/> 200 * Important: The match is NOT done on object identity. 201 * Instead, this searches for a <em>similar</em> source, based on 202 * {@link SdkSource#equals(Object)} which compares the source URLs. 203 * <p/> 204 * The search is O(N), which should be acceptable on the expectedly small source list. 205 */ 206 public boolean hasSourceUrl(SdkSource source) { 207 synchronized (mSources) { 208 for (ArrayList<SdkSource> list : mSources.values()) { 209 for (SdkSource s : list) { 210 if (s.equals(source)) { 211 return true; 212 } 213 } 214 } 215 return false; 216 } 217 } 218 219 /** 220 * Returns true if there's already a similar source in the sources list 221 * under the specified category. 222 * <p/> 223 * Important: The match is NOT done on object identity. 224 * Instead, this searches for a <em>similar</em> source, based on 225 * {@link SdkSource#equals(Object)} which compares the source URLs. 226 * <p/> 227 * The search is O(N), which should be acceptable on the expectedly small source list. 228 */ 229 public boolean hasSourceUrl(SdkSourceCategory category, SdkSource source) { 230 synchronized (mSources) { 231 ArrayList<SdkSource> list = mSources.get(category); 232 if (list != null) { 233 for (SdkSource s : list) { 234 if (s.equals(source)) { 235 return true; 236 } 237 } 238 } 239 return false; 240 } 241 } 242 243 /** 244 * Loads all user sources. This <em>replaces</em> all existing user sources 245 * by the ones from the property file. 246 */ 247 public void loadUserAddons(ISdkLog log) { 248 249 // Remove all existing user sources 250 removeAll(SdkSourceCategory.USER_ADDONS); 251 252 // Load new user sources from property file 253 FileInputStream fis = null; 254 try { 255 String folder = AndroidLocation.getFolder(); 256 File f = new File(folder, SRC_FILENAME); 257 if (f.exists()) { 258 fis = new FileInputStream(f); 259 260 Properties props = new Properties(); 261 props.load(fis); 262 263 int count = Integer.parseInt(props.getProperty(KEY_COUNT, "0")); 264 265 for (int i = 0; i < count; i++) { 266 String url = props.getProperty(String.format("%s%02d", KEY_SRC, i)); //$NON-NLS-1$ 267 if (url != null) { 268 SdkSource s = new SdkAddonSource(url, null/*uiName*/); 269 if (!hasSourceUrl(s)) { 270 add(SdkSourceCategory.USER_ADDONS, s); 271 } 272 } 273 } 274 } 275 276 } catch (NumberFormatException e) { 277 log.error(e, null); 278 279 } catch (AndroidLocationException e) { 280 log.error(e, null); 281 282 } catch (IOException e) { 283 log.error(e, null); 284 285 } finally { 286 if (fis != null) { 287 try { 288 fis.close(); 289 } catch (IOException e) { 290 } 291 } 292 } 293 } 294 295 /** 296 * Saves all the user sources. 297 * @param log Logger. Cannot be null. 298 */ 299 public void saveUserAddons(ISdkLog log) { 300 FileOutputStream fos = null; 301 try { 302 String folder = AndroidLocation.getFolder(); 303 File f = new File(folder, SRC_FILENAME); 304 305 fos = new FileOutputStream(f); 306 307 Properties props = new Properties(); 308 309 int count = 0; 310 for (SdkSource s : getSources(SdkSourceCategory.USER_ADDONS)) { 311 props.setProperty(String.format("%s%02d", KEY_SRC, count), s.getUrl()); //$NON-NLS-1$ 312 count++; 313 } 314 props.setProperty(KEY_COUNT, Integer.toString(count)); 315 316 props.store( fos, "## User Sources for Android tool"); //$NON-NLS-1$ 317 318 } catch (AndroidLocationException e) { 319 log.error(e, null); 320 321 } catch (IOException e) { 322 log.error(e, null); 323 324 } finally { 325 if (fos != null) { 326 try { 327 fos.close(); 328 } catch (IOException e) { 329 } 330 } 331 } 332 333 } 334 } 335