1 /* 2 * Copyright (C) 2012 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.sources; 18 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.annotations.VisibleForTesting; 22 import com.android.annotations.VisibleForTesting.Visibility; 23 import com.android.prefs.AndroidLocation; 24 import com.android.prefs.AndroidLocation.AndroidLocationException; 25 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.util.List; 33 import java.util.Properties; 34 35 /** 36 * Properties for individual sources which are persisted by a local settings file. 37 * <p/> 38 * All instances of {@link SdkSourceProperties} share the same singleton storage. 39 * The persisted setting file is loaded as necessary, however callers must persist 40 * it at some point by calling {@link #save()}. 41 */ 42 public class SdkSourceProperties { 43 44 /** 45 * An internal file version number, in case we want to change the format later. 46 */ 47 private static final String KEY_VERSION = "@version@"; //$NON-NLS-1$ 48 /** 49 * The last known UI name of the source. 50 */ 51 public static final String KEY_NAME = "@name@"; //$NON-NLS-1$ 52 /** 53 * A non-null string if the source is disabled. Null if the source is enabled. 54 */ 55 public static final String KEY_DISABLED = "@disabled@"; //$NON-NLS-1$ 56 57 private static final Properties sSourcesProperties = new Properties(); 58 private static final String SRC_FILENAME = "sites-settings.cfg"; //$NON-NLS-1$ 59 60 private static boolean sModified = false; 61 SdkSourceProperties()62 public SdkSourceProperties() { 63 } 64 save()65 public void save() { 66 synchronized (sSourcesProperties) { 67 if (sModified && !sSourcesProperties.isEmpty()) { 68 saveLocked(); 69 sModified = false; 70 } 71 } 72 } 73 74 /** 75 * Retrieves a property for the given source URL and the given key type. 76 * <p/> 77 * Implementation detail: this loads the persistent settings file as needed. 78 * 79 * @param key The kind of property to retrieve for that source URL. 80 * @param sourceUrl The source URL. 81 * @param defaultValue The default value to return, if the property isn't found. Can be null. 82 * @return The non-null string property for the key/sourceUrl or the default value. 83 */ 84 @Nullable getProperty(@onNull String key, @NonNull String sourceUrl, @Nullable String defaultValue)85 public String getProperty(@NonNull String key, 86 @NonNull String sourceUrl, 87 @Nullable String defaultValue) { 88 String value = defaultValue; 89 90 synchronized (sSourcesProperties) { 91 if (sSourcesProperties.isEmpty()) { 92 loadLocked(); 93 } 94 95 value = sSourcesProperties.getProperty(key + sourceUrl, defaultValue); 96 } 97 98 return value; 99 } 100 101 /** 102 * Sets or remove a property for the given source URL and the given key type. 103 * <p/> 104 * Implementation detail: this does <em>not</em> save the persistent settings file. 105 * Somehow the caller will need to call the {@link #save()} method later. 106 * 107 * @param key The kind of property to retrieve for that source URL. 108 * @param sourceUrl The source URL. 109 * @param value The new value to set (if non null) or null to remove an existing property. 110 */ setProperty(String key, String sourceUrl, String value)111 public void setProperty(String key, String sourceUrl, String value) { 112 synchronized (sSourcesProperties) { 113 if (sSourcesProperties.isEmpty()) { 114 loadLocked(); 115 } 116 117 key += sourceUrl; 118 119 String old = sSourcesProperties.getProperty(key); 120 if (value == null) { 121 if (old != null) { 122 sSourcesProperties.remove(key); 123 sModified = true; 124 } 125 } else if (old == null || !old.equals(value)) { 126 sSourcesProperties.setProperty(key, value); 127 sModified = true; 128 } 129 } 130 } 131 132 /** 133 * Returns an internal string representation of the underlying Properties map, 134 * sorted by ascending keys. Useful for debugging and testing purposes only. 135 */ 136 @Override toString()137 public String toString() { 138 StringBuilder sb = new StringBuilder("<SdkSourceProperties"); //$NON-NLS-1$ 139 synchronized (sSourcesProperties) { 140 List<Object> keys = Collections.list(sSourcesProperties.keys()); 141 Collections.sort(keys, new Comparator<Object>() { 142 @Override 143 public int compare(Object o1, Object o2) { 144 return o1.toString().compareTo(o2.toString()); 145 }}); 146 147 for (Object key : keys) { 148 sb.append('\n').append(key) 149 .append(" = ").append(sSourcesProperties.get(key)); //$NON-NLS-1$ 150 } 151 } 152 sb.append('>'); 153 return sb.toString(); 154 } 155 156 /** Load state from persistent file. Expects sSourcesProperties to be synchronized. */ loadLocked()157 private void loadLocked() { 158 // Load state from persistent file 159 if (loadProperties()) { 160 // If it lacks our magic version key, don't use it 161 if (sSourcesProperties.getProperty(KEY_VERSION) == null) { 162 sSourcesProperties.clear(); 163 } 164 165 sModified = false; 166 } 167 168 if (sSourcesProperties.isEmpty()) { 169 // Nothing was loaded. Initialize the storage with a version 170 // identified. This isn't currently checked back, but we might 171 // want it later if we decide to change the way this works. 172 // The version key is choosen on purpose to not match any valid URL. 173 sSourcesProperties.setProperty(KEY_VERSION, "1"); //$NON-NLS-1$ //$NON-NLS-2$ 174 } 175 } 176 177 /** 178 * Load properties from default file. Extracted so that it can be mocked in tests. 179 * 180 * @return True if actually loaded the file. False if there was an IO error or no 181 * file and nothing was loaded. 182 */ 183 @VisibleForTesting(visibility=Visibility.PRIVATE) loadProperties()184 protected boolean loadProperties() { 185 try { 186 String folder = AndroidLocation.getFolder(); 187 File f = new File(folder, SRC_FILENAME); 188 if (f.exists()) { 189 FileInputStream fis = null; 190 try { 191 fis = new FileInputStream(f); 192 sSourcesProperties.load(fis); 193 } catch (IOException ignore) { 194 // nop 195 } finally { 196 if (fis != null) { 197 try { 198 fis.close(); 199 } catch (IOException ignore) {} 200 } 201 } 202 203 return true; 204 } 205 } catch (AndroidLocationException ignore) { 206 // nop 207 } 208 return false; 209 } 210 211 /** 212 * Save file to disk. Expects sSourcesProperties to be synchronized. 213 * Made accessible for testing purposes. 214 * For public usage, please use {@link #save()} instead. 215 */ 216 @VisibleForTesting(visibility=Visibility.PRIVATE) saveLocked()217 protected void saveLocked() { 218 // Persist it to the file 219 FileOutputStream fos = null; 220 try { 221 String folder = AndroidLocation.getFolder(); 222 File f = new File(folder, SRC_FILENAME); 223 224 fos = new FileOutputStream(f); 225 226 sSourcesProperties.store(fos,"## Sites Settings for Android SDK Manager");//$NON-NLS-1$ 227 228 } catch (AndroidLocationException ignore) { 229 // nop 230 } catch (IOException ignore) { 231 // nop 232 } finally { 233 if (fos != null) { 234 try { 235 fos.close(); 236 } catch (IOException ignore) {} 237 } 238 } 239 } 240 241 /** Empty current property list. Made accessible for testing purposes. */ 242 @VisibleForTesting(visibility=Visibility.PRIVATE) clear()243 protected void clear() { 244 synchronized (sSourcesProperties) { 245 sSourcesProperties.clear(); 246 sModified = false; 247 } 248 } 249 } 250