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