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        for (PluginInformation pi: availablePlugins) {
240            if (pi.getName() != null && pi.getName().equals(name))
241                return pi;
242        }
243        return null;
244    }
245
246    /**
247     * Initializes the model from preferences
248     */
249    public void initFromPreferences() {
250        Collection<String> enabledPlugins = Config.getPref().getList("plugins", null);
251        if (enabledPlugins == null) {
252            this.selectedPluginsMap.clear();
253            return;
254        }
255        for (String name: enabledPlugins) {
256            PluginInformation pi = getPluginInformation(name);
257            if (pi == null) {
258                continue;
259            }
260            setPluginSelected(name, true);
261        }
262    }
263
264    /**
265     * Replies true if the plugin with name <code>name</code> is currently
266     * selected in the plugin model
267     *
268     * @param name the plugin name
269     * @return true if the plugin is selected; false, otherwise
270     */
271    public boolean isSelectedPlugin(String name) {
272        PluginInformation pi = getPluginInformation(name);
273        if (pi == null || selectedPluginsMap.get(pi) == null)
274            return false;
275        return selectedPluginsMap.get(pi);
276    }
277
278    /**
279     * Replies the set of plugins which have been added by the user to
280     * the set of activated plugins.
281     *
282     * @return the set of newly activated plugins
283     */
284    public List<PluginInformation> getNewlyActivatedPlugins() {
285        List<PluginInformation> ret = new LinkedList<>();
286        for (Entry<PluginInformation, Boolean> entry: selectedPluginsMap.entrySet()) {
287            PluginInformation pi = entry.getKey();
288            boolean selected = entry.getValue();
289            if (selected && !currentActivePlugins.contains(pi.name)) {
290                ret.add(pi);
291            }
292        }
293        return ret;
294    }
295
296    /**
297     * Replies the set of plugins which have been removed by the user from
298     * the set of deactivated plugins.
299     *
300     * @return the set of newly deactivated plugins
301     */
302    public List<PluginInformation> getNewlyDeactivatedPlugins() {
303        List<PluginInformation> ret = new LinkedList<>();
304        for (PluginInformation pi: availablePlugins) {
305            if (!currentActivePlugins.contains(pi.name)) {
306                continue;
307            }
308            if (selectedPluginsMap.get(pi) == null || !selectedPluginsMap.get(pi)) {
309                ret.add(pi);
310            }
311        }
312        return ret;
313    }
314
315    /**
316     * Replies the set of all available plugins.
317     *
318     * @return the set of all available plugins
319     */
320    public List<PluginInformation> getAvailablePlugins() {
321        return new LinkedList<>(availablePlugins);
322    }
323
324    /**
325     * Replies the set of plugin names which have been added by the user to
326     * the set of activated plugins.
327     *
328     * @return the set of newly activated plugin names
329     */
330    public Set<String> getNewlyActivatedPluginNames() {
331        Set<String> ret = new HashSet<>();
332        List<PluginInformation> plugins = getNewlyActivatedPlugins();
333        for (PluginInformation pi: plugins) {
334            ret.add(pi.name);
335        }
336        return ret;
337    }
338
339    /**
340     * Replies true if the set of active plugins has been changed by the user
341     * in this preference model. He has either added plugins or removed plugins
342     * being active before.
343     *
344     * @return true if the collection of active plugins has changed
345     */
346    public boolean isActivePluginsChanged() {
347        Set<String> newActivePlugins = getSelectedPluginNames();
348        return !newActivePlugins.equals(currentActivePlugins);
349    }
350
351    /**
352     * Refreshes the local version field on the plugins in <code>plugins</code> with
353     * the version in the manifest of the downloaded "jar.new"-file for this plugin.
354     *
355     * @param plugins the collections of plugins to refresh
356     */
357    public void refreshLocalPluginVersion(Collection<PluginInformation> plugins) {
358        if (plugins != null) {
359            for (PluginInformation pi : plugins) {
360                File downloadedPluginFile = PluginHandler.findUpdatedJar(pi.name);
361                if (downloadedPluginFile == null) {
362                    continue;
363                }
364                try {
365                    PluginInformation newinfo = new PluginInformation(downloadedPluginFile, pi.name);
366                    PluginInformation oldinfo = getPluginInformation(pi.name);
367                    if (oldinfo != null) {
368                        oldinfo.updateLocalInfo(newinfo);
369                    }
370                } catch (PluginException e) {
371                    Logging.error(e);
372                }
373            }
374        }
375    }
376}