001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.plugin; 003 004import java.io.File; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Comparator; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.LinkedList; 011import java.util.List; 012import java.util.Locale; 013import java.util.Map; 014import java.util.Map.Entry; 015import java.util.Set; 016 017import org.openstreetmap.josm.gui.util.ChangeNotifier; 018import org.openstreetmap.josm.plugins.PluginException; 019import org.openstreetmap.josm.plugins.PluginHandler; 020import org.openstreetmap.josm.plugins.PluginInformation; 021import org.openstreetmap.josm.spi.preferences.Config; 022import org.openstreetmap.josm.tools.Logging; 023 024/** 025 * The plugin model behind a {@code PluginListPanel}. 026 */ 027public class PluginPreferencesModel extends ChangeNotifier { 028 // remember the initial list of active plugins 029 private final Set<String> currentActivePlugins; 030 private final List<PluginInformation> availablePlugins = new ArrayList<>(); 031 private PluginInstallation filterStatus; 032 private String filterExpression; 033 private final List<PluginInformation> displayedPlugins = new ArrayList<>(); 034 private final Map<PluginInformation, Boolean> selectedPluginsMap = new HashMap<>(); 035 // plugins that still require an update/download 036 private final Set<String> pendingDownloads = new HashSet<>(); 037 038 /** 039 * Constructs a new {@code PluginPreferencesModel}. 040 */ 041 public PluginPreferencesModel() { 042 currentActivePlugins = new HashSet<>(); 043 currentActivePlugins.addAll(Config.getPref().getList("plugins")); 044 } 045 046 /** 047 * Filters the list of displayed plugins by installation status. 048 * @param status The filter used against installation status 049 * @since 13799 050 */ 051 public void filterDisplayedPlugins(PluginInstallation status) { 052 filterStatus = status; 053 doFilter(); 054 } 055 056 /** 057 * Filters the list of displayed plugins by text. 058 * @param filter The filter used against plugin name, description or version 059 */ 060 public void filterDisplayedPlugins(String filter) { 061 filterExpression = filter; 062 doFilter(); 063 } 064 065 private void doFilter() { 066 displayedPlugins.clear(); 067 for (PluginInformation pi: availablePlugins) { 068 if ((filterStatus == null || matchesInstallationStatus(pi)) 069 && (filterExpression == null || pi.matches(filterExpression))) { 070 displayedPlugins.add(pi); 071 } 072 } 073 fireStateChanged(); 074 } 075 076 private boolean matchesInstallationStatus(PluginInformation pi) { 077 boolean installed = currentActivePlugins.contains(pi.getName()); 078 return PluginInstallation.ALL == filterStatus 079 || (PluginInstallation.INSTALLED == filterStatus && installed) 080 || (PluginInstallation.AVAILABLE == filterStatus && !installed); 081 } 082 083 /** 084 * Sets the list of available plugins. 085 * @param available The available plugins 086 */ 087 public void setAvailablePlugins(Collection<PluginInformation> available) { 088 availablePlugins.clear(); 089 if (available != null) { 090 availablePlugins.addAll(available); 091 } 092 availablePluginsModified(); 093 } 094 095 protected final void availablePluginsModified() { 096 sort(); 097 filterDisplayedPlugins(filterStatus); 098 filterDisplayedPlugins(filterExpression); 099 Set<String> activePlugins = new HashSet<>(); 100 activePlugins.addAll(Config.getPref().getList("plugins")); 101 for (PluginInformation pi: availablePlugins) { 102 if (selectedPluginsMap.get(pi) == null && activePlugins.contains(pi.name)) { 103 selectedPluginsMap.put(pi, Boolean.TRUE); 104 } 105 } 106 fireStateChanged(); 107 } 108 109 protected void updateAvailablePlugin(PluginInformation other) { 110 if (other != null) { 111 PluginInformation pi = getPluginInformation(other.name); 112 if (pi == null) { 113 availablePlugins.add(other); 114 return; 115 } 116 pi.updateFromPluginSite(other); 117 } 118 } 119 120 /** 121 * Updates the list of plugin information objects with new information from 122 * plugin update sites. 123 * 124 * @param fromPluginSite plugin information read from plugin update sites 125 */ 126 public void updateAvailablePlugins(Collection<PluginInformation> fromPluginSite) { 127 for (PluginInformation other: fromPluginSite) { 128 updateAvailablePlugin(other); 129 } 130 availablePluginsModified(); 131 } 132 133 /** 134 * Replies the list of selected plugin information objects 135 * 136 * @return the list of selected plugin information objects 137 */ 138 public List<PluginInformation> getSelectedPlugins() { 139 List<PluginInformation> ret = new LinkedList<>(); 140 for (PluginInformation pi: availablePlugins) { 141 if (selectedPluginsMap.get(pi) == null) { 142 continue; 143 } 144 if (selectedPluginsMap.get(pi)) { 145 ret.add(pi); 146 } 147 } 148 return ret; 149 } 150 151 /** 152 * Replies the list of selected plugin information objects 153 * 154 * @return the list of selected plugin information objects 155 */ 156 public Set<String> getSelectedPluginNames() { 157 Set<String> ret = new HashSet<>(); 158 for (PluginInformation pi: getSelectedPlugins()) { 159 ret.add(pi.name); 160 } 161 return ret; 162 } 163 164 /** 165 * Sorts the list of available plugins 166 */ 167 protected void sort() { 168 availablePlugins.sort(Comparator.comparing( 169 o -> o.getName() == null ? "" : o.getName().toLowerCase(Locale.ENGLISH))); 170 } 171 172 /** 173 * Replies the list of plugin informations to display. 174 * 175 * @return the list of plugin informations to display 176 */ 177 public List<PluginInformation> getDisplayedPlugins() { 178 return displayedPlugins; 179 } 180 181 /** 182 * Replies the set of plugins waiting for update or download. 183 * 184 * @return the set of plugins waiting for update or download 185 */ 186 public Set<PluginInformation> getPluginsScheduledForUpdateOrDownload() { 187 Set<PluginInformation> ret = new HashSet<>(); 188 for (String plugin: pendingDownloads) { 189 PluginInformation pi = getPluginInformation(plugin); 190 if (pi == null) { 191 continue; 192 } 193 ret.add(pi); 194 } 195 return ret; 196 } 197 198 /** 199 * Sets whether the plugin is selected or not. 200 * 201 * @param name the name of the plugin 202 * @param selected true, if selected; false, otherwise 203 */ 204 public void setPluginSelected(String name, boolean selected) { 205 PluginInformation pi = getPluginInformation(name); 206 if (pi != null) { 207 selectedPluginsMap.put(pi, selected); 208 if (pi.isUpdateRequired()) { 209 pendingDownloads.add(pi.name); 210 } 211 } 212 if (!selected) { 213 pendingDownloads.remove(name); 214 } 215 } 216 217 /** 218 * Removes all the plugin in {@code plugins} from the list of plugins 219 * with a pending download 220 * 221 * @param plugins the list of plugins to clear for a pending download 222 */ 223 public void clearPendingPlugins(Collection<PluginInformation> plugins) { 224 if (plugins != null) { 225 for (PluginInformation pi: plugins) { 226 pendingDownloads.remove(pi.name); 227 } 228 } 229 } 230 231 /** 232 * Replies the plugin info with the name <code>name</code>. null, if no 233 * such plugin info exists. 234 * 235 * @param name the name. If null, replies null. 236 * @return the plugin info. 237 */ 238 public PluginInformation getPluginInformation(String name) { 239 if (name != null) { 240 for (PluginInformation pi: availablePlugins) { 241 if (name.equals(pi.getName()) || name.equals(pi.provides)) 242 return pi; 243 } 244 } 245 return null; 246 } 247 248 /** 249 * Initializes the model from preferences 250 */ 251 public void initFromPreferences() { 252 Collection<String> enabledPlugins = Config.getPref().getList("plugins", null); 253 if (enabledPlugins == null) { 254 this.selectedPluginsMap.clear(); 255 return; 256 } 257 for (String name: enabledPlugins) { 258 PluginInformation pi = getPluginInformation(name); 259 if (pi == null) { 260 continue; 261 } 262 setPluginSelected(name, true); 263 } 264 } 265 266 /** 267 * Replies true if the plugin with name <code>name</code> is currently 268 * selected in the plugin model 269 * 270 * @param name the plugin name 271 * @return true if the plugin is selected; false, otherwise 272 */ 273 public boolean isSelectedPlugin(String name) { 274 PluginInformation pi = getPluginInformation(name); 275 if (pi == null || selectedPluginsMap.get(pi) == null) 276 return false; 277 return selectedPluginsMap.get(pi); 278 } 279 280 /** 281 * Replies the set of plugins which have been added by the user to 282 * the set of activated plugins. 283 * 284 * @return the set of newly activated plugins 285 */ 286 public List<PluginInformation> getNewlyActivatedPlugins() { 287 List<PluginInformation> ret = new LinkedList<>(); 288 for (Entry<PluginInformation, Boolean> entry: selectedPluginsMap.entrySet()) { 289 PluginInformation pi = entry.getKey(); 290 boolean selected = entry.getValue(); 291 if (selected && !currentActivePlugins.contains(pi.name)) { 292 ret.add(pi); 293 } 294 } 295 return ret; 296 } 297 298 /** 299 * Replies the set of plugins which have been removed by the user from 300 * the set of deactivated plugins. 301 * 302 * @return the set of newly deactivated plugins 303 */ 304 public List<PluginInformation> getNewlyDeactivatedPlugins() { 305 List<PluginInformation> ret = new LinkedList<>(); 306 for (PluginInformation pi: availablePlugins) { 307 if (!currentActivePlugins.contains(pi.name)) { 308 continue; 309 } 310 if (selectedPluginsMap.get(pi) == null || !selectedPluginsMap.get(pi)) { 311 ret.add(pi); 312 } 313 } 314 return ret; 315 } 316 317 /** 318 * Replies the set of all available plugins. 319 * 320 * @return the set of all available plugins 321 */ 322 public List<PluginInformation> getAvailablePlugins() { 323 return new LinkedList<>(availablePlugins); 324 } 325 326 /** 327 * Replies the set of plugin names which have been added by the user to 328 * the set of activated plugins. 329 * 330 * @return the set of newly activated plugin names 331 */ 332 public Set<String> getNewlyActivatedPluginNames() { 333 Set<String> ret = new HashSet<>(); 334 List<PluginInformation> plugins = getNewlyActivatedPlugins(); 335 for (PluginInformation pi: plugins) { 336 ret.add(pi.name); 337 } 338 return ret; 339 } 340 341 /** 342 * Replies true if the set of active plugins has been changed by the user 343 * in this preference model. He has either added plugins or removed plugins 344 * being active before. 345 * 346 * @return true if the collection of active plugins has changed 347 */ 348 public boolean isActivePluginsChanged() { 349 Set<String> newActivePlugins = getSelectedPluginNames(); 350 return !newActivePlugins.equals(currentActivePlugins); 351 } 352 353 /** 354 * Refreshes the local version field on the plugins in <code>plugins</code> with 355 * the version in the manifest of the downloaded "jar.new"-file for this plugin. 356 * 357 * @param plugins the collections of plugins to refresh 358 */ 359 public void refreshLocalPluginVersion(Collection<PluginInformation> plugins) { 360 if (plugins != null) { 361 for (PluginInformation pi : plugins) { 362 File downloadedPluginFile = PluginHandler.findUpdatedJar(pi.name); 363 if (downloadedPluginFile == null) { 364 continue; 365 } 366 try { 367 PluginInformation newinfo = new PluginInformation(downloadedPluginFile, pi.name); 368 PluginInformation oldinfo = getPluginInformation(pi.name); 369 if (oldinfo != null) { 370 oldinfo.updateFromJar(newinfo); 371 } 372 } catch (PluginException e) { 373 Logging.error(e); 374 } 375 } 376 } 377 } 378}