Browse Source

Split out a PluginFileHandler.

pull/576/head
Chris Smith 9 years ago
parent
commit
149dc5af81

+ 215
- 0
src/com/dmdirc/plugins/PluginFileHandler.java View File

@@ -0,0 +1,215 @@
1
+/*
2
+ * Copyright (c) 2006-2015 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.plugins;
24
+
25
+import com.dmdirc.DMDircMBassador;
26
+import com.dmdirc.commandline.CommandLineOptionsModule.Directory;
27
+import com.dmdirc.commandline.CommandLineOptionsModule.DirectoryType;
28
+import com.dmdirc.events.UserErrorEvent;
29
+import com.dmdirc.logger.ErrorLevel;
30
+
31
+import com.google.common.collect.ArrayListMultimap;
32
+import com.google.common.collect.Multimap;
33
+
34
+import java.io.IOException;
35
+import java.net.MalformedURLException;
36
+import java.net.URL;
37
+import java.nio.file.FileVisitOption;
38
+import java.nio.file.Files;
39
+import java.nio.file.Path;
40
+import java.util.ArrayList;
41
+import java.util.Collection;
42
+import java.util.Collections;
43
+import java.util.Map;
44
+import java.util.Objects;
45
+import java.util.concurrent.CopyOnWriteArrayList;
46
+import java.util.function.Function;
47
+import java.util.stream.Collectors;
48
+
49
+import javax.annotation.Nullable;
50
+import javax.inject.Inject;
51
+import javax.inject.Singleton;
52
+
53
+/**
54
+ * Locates and tracks plugin files on disk.
55
+ */
56
+@Singleton
57
+public class PluginFileHandler {
58
+
59
+    private final Path directory;
60
+    private final DMDircMBassador eventBus;
61
+
62
+    private final Collection<PluginMetaData> knownPlugins = new CopyOnWriteArrayList<>();
63
+
64
+    @Inject
65
+    public PluginFileHandler(
66
+            @Directory(DirectoryType.PLUGINS) final Path directory,
67
+            final DMDircMBassador eventBus) {
68
+        this.directory = directory;
69
+        this.eventBus = eventBus;
70
+    }
71
+
72
+    /**
73
+     * Finds all plugin files on disk, loads their metadata, and validates them.
74
+     *
75
+     * @param manager The plugin manager to pass to new metadata instances.
76
+     * @return Collection of valid plugins.
77
+     */
78
+    public Collection<PluginMetaData> refresh(final PluginManager manager) {
79
+        final Collection<PluginMetaData> metadata = findAllPlugins(manager);
80
+
81
+        // Deal with plugins that had errors
82
+        metadata.stream().filter(PluginMetaData::hasErrors).forEach(this::reportErrors);
83
+        metadata.removeIf(PluginMetaData::hasErrors);
84
+
85
+        final Collection<PluginMetaData> newPlugins = getValidPlugins(metadata);
86
+
87
+        knownPlugins.clear();
88
+        knownPlugins.addAll(newPlugins);
89
+
90
+        return newPlugins;
91
+    }
92
+
93
+    /**
94
+     * Gets the collection of known plugins.
95
+     *
96
+     * @return A cached collection of plugins (no disk I/O will be performed).
97
+     */
98
+    public Collection<PluginMetaData> getKnownPlugins() {
99
+        return Collections.unmodifiableCollection(knownPlugins);
100
+    }
101
+
102
+    /**
103
+     * Finds all plugins on disk with loadable metadata.
104
+     *
105
+     * @param manager The plugin manager to pass to new metadata instances.
106
+     * @return Collection of all plugins with loadable metadata.
107
+     */
108
+    private Collection<PluginMetaData> findAllPlugins(final PluginManager manager) {
109
+        try {
110
+            return Files.walk(directory, FileVisitOption.FOLLOW_LINKS)
111
+                    .filter(p -> p.getFileName().toString().endsWith(".jar"))
112
+                    .map(Path::toAbsolutePath)
113
+                    .map(path -> this.getMetaData(path, manager)).filter(Objects::nonNull)
114
+                    .collect(Collectors.toList());
115
+        } catch (IOException ex) {
116
+            eventBus.publish(new UserErrorEvent(ErrorLevel.HIGH, ex,
117
+                    "Unable to read plugin directory", ""));
118
+            return Collections.emptyList();
119
+        }
120
+    }
121
+
122
+    /**
123
+     * Attempts to get the metadata for a plugin at the specified path.
124
+     *
125
+     * @param path The path of the plugin to get metadata from.
126
+     * @param manager The plugin manager to pass to new metadata instances.
127
+     * @return The metadata if it could be created, {@code null} otherwise.
128
+     */
129
+    @Nullable
130
+    private PluginMetaData getMetaData(final Path path, final PluginManager manager) {
131
+        try {
132
+            final PluginMetaData metaData = new PluginMetaData(
133
+                    manager, new URL("jar:file:" + path + "!/META-INF/plugin.config"), path);
134
+            metaData.load();
135
+            return metaData;
136
+        } catch (MalformedURLException ex) {
137
+            eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, ex,
138
+                    "Error creating URL for plugin " + path + ": " + ex.getMessage(), ""));
139
+            return null;
140
+        }
141
+    }
142
+
143
+    /**
144
+     * Reports any errors present in the metadata to the event bus.
145
+     *
146
+     * @param pluginMetaData The metadata to read errors from.
147
+     */
148
+    private void reportErrors(final PluginMetaData pluginMetaData) {
149
+        eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, null,
150
+                "Error reading plugin metadata for plugin " + pluginMetaData.getPluginPath() + ": "
151
+                        + pluginMetaData.getErrors(), ""));
152
+    }
153
+
154
+    /**
155
+     * Gets all services defined for the given plugins.
156
+     *
157
+     * @param metadata The metadata to retrieve services from.
158
+     * @return A multimap of service types to their implementations.
159
+     */
160
+    private Multimap<String, String> getServices(final Collection<PluginMetaData> metadata) {
161
+        final Multimap<String, String> services = ArrayListMultimap.create();
162
+
163
+        // Normal services
164
+        metadata.stream()
165
+                .map(PluginMetaData::getServices)
166
+                .flatMap(Collection::stream)
167
+                .map(service -> service.split(" ", 2))
168
+                .forEach(pair -> services.put(pair[1], pair[0]));
169
+
170
+        // Exports
171
+        metadata.stream()
172
+                .map(PluginMetaData::getExports)
173
+                .flatMap(Collection::stream)
174
+                .map(exportName -> exportName.split(" "))
175
+                .map(parts -> parts.length > 4 ? parts[4] : parts[0])
176
+                .forEach(name -> services.put("export", name));
177
+
178
+        return services;
179
+    }
180
+
181
+    /**
182
+     * Validates each plugin uses a {@link PluginMetaDataValidator}, and returns those found to
183
+     * be valid.
184
+     *
185
+     * @param metadata The collection of metadata to validate.
186
+     * @return The collection of metadata that passed validation.
187
+     */
188
+    private Collection<PluginMetaData> getValidPlugins(
189
+            final Collection<PluginMetaData> metadata) {
190
+        // Collect a map by name
191
+        final Map<String, PluginMetaData> metaDataByName = metadata.stream()
192
+                .collect(Collectors.toMap(PluginMetaData::getName, Function.identity()));
193
+
194
+        // Collect up services
195
+        final Multimap<String, String> services = getServices(metadata);
196
+
197
+        // Validate each in turn
198
+        final Collection<PluginMetaData> res = new ArrayList<>();
199
+        for (PluginMetaData target : metadata) {
200
+            final PluginMetaDataValidator validator = new PluginMetaDataValidator(target);
201
+            final Collection<String> results = validator.validate(metaDataByName, services);
202
+
203
+            if (results.isEmpty()) {
204
+                res.add(target);
205
+            } else {
206
+                eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, null,
207
+                        "Plugin validation failed for " + target.getPluginPath() + ": " + results,
208
+                        ""));
209
+            }
210
+        }
211
+
212
+        return res;
213
+    }
214
+
215
+}

+ 5
- 72
src/com/dmdirc/plugins/PluginManager.java View File

@@ -30,9 +30,6 @@ import com.dmdirc.logger.ErrorLevel;
30 30
 import com.dmdirc.updater.components.PluginComponent;
31 31
 import com.dmdirc.updater.manager.UpdateManager;
32 32
 
33
-import com.google.common.collect.ArrayListMultimap;
34
-import com.google.common.collect.Multimap;
35
-
36 33
 import java.io.File;
37 34
 import java.net.MalformedURLException;
38 35
 import java.net.URL;
@@ -58,6 +55,7 @@ public class PluginManager {
58 55
     private final Map<String, PluginInfo> knownPlugins = new HashMap<>();
59 56
     /** Set of known plugins' metadata. */
60 57
     private final Collection<PluginMetaData> plugins = new HashSet<>();
58
+    private final PluginFileHandler fileHandler;
61 59
     /** Directory where plugins are stored. */
62 60
     private final String directory;
63 61
     /** The identity controller to use to find configuration options. */
@@ -88,10 +86,12 @@ public class PluginManager {
88 86
             final IdentityController identityController,
89 87
             final UpdateManager updateManager,
90 88
             final ObjectGraph objectGraph,
89
+            final PluginFileHandler fileHandler,
91 90
             final String directory) {
92 91
         this.identityController = identityController;
93 92
         this.serviceManager = serviceManager;
94 93
         this.updateManager = updateManager;
94
+        this.fileHandler = fileHandler;
95 95
         this.directory = directory;
96 96
         this.globalClassLoader = new GlobalClassLoader(this);
97 97
         this.objectGraph = objectGraph;
@@ -302,7 +302,7 @@ public class PluginManager {
302 302
     public void refreshPlugins() {
303 303
         applyUpdates();
304 304
 
305
-        final Collection<PluginMetaData> newPlugins = getAllPlugins();
305
+        final Collection<PluginMetaData> newPlugins = fileHandler.refresh(this);
306 306
 
307 307
         for (PluginMetaData plugin : newPlugins) {
308 308
             addPlugin(plugin.getRelativeFilename());
@@ -352,74 +352,7 @@ public class PluginManager {
352 352
      * @return A list of all installed or known plugins
353 353
      */
354 354
     public Collection<PluginMetaData> getAllPlugins() {
355
-        final Collection<PluginMetaData> res = new HashSet<>(plugins.size());
356
-
357
-        final Deque<File> dirs = new LinkedList<>();
358
-        final Collection<String> pluginPaths = new LinkedList<>();
359
-
360
-        dirs.add(new File(directory));
361
-
362
-        while (!dirs.isEmpty()) {
363
-            final File dir = dirs.pop();
364
-            if (dir.isDirectory()) {
365
-                dirs.addAll(Arrays.asList(dir.listFiles()));
366
-            } else if (dir.isFile() && dir.getName().endsWith(".jar")) {
367
-                pluginPaths.add(dir.getPath().substring(directory.length()));
368
-            }
369
-        }
370
-
371
-        final Multimap<String, String> newServices = ArrayListMultimap.create();
372
-        final Map<String, PluginMetaData> newPluginsByName = new HashMap<>();
373
-        final Map<String, PluginMetaData> newPluginsByPath = new HashMap<>();
374
-
375
-        // Initialise all of our metadata objects
376
-        for (String target : pluginPaths) {
377
-            try {
378
-                final PluginMetaData targetMetaData = new PluginMetaData(this,
379
-                        new URL("jar:file:" + directory + target
380
-                                + "!/META-INF/plugin.config"),
381
-                        Paths.get(directory, target));
382
-                targetMetaData.load();
383
-
384
-                if (targetMetaData.hasErrors()) {
385
-                    eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, null,
386
-                            "Error reading plugin metadata for plugin " + target
387
-                            + ": " + targetMetaData.getErrors(), ""));
388
-                } else {
389
-                    newPluginsByName.put(targetMetaData.getName(), targetMetaData);
390
-                    newPluginsByPath.put(target, targetMetaData);
391
-
392
-                    for (String service : targetMetaData.getServices()) {
393
-                        final String[] parts = service.split(" ", 2);
394
-                        newServices.put(parts[1], parts[0]);
395
-                    }
396
-
397
-                    for (String export : targetMetaData.getExports()) {
398
-                        final String[] parts = export.split(" ");
399
-                        final String name = parts.length > 4 ? parts[4] : parts[0];
400
-                        newServices.put("export", name);
401
-                    }
402
-                }
403
-            } catch (MalformedURLException mue) {
404
-                eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, mue,
405
-                        "Error creating URL for plugin " + target + ": " + mue.getMessage(), ""));
406
-            }
407
-        }
408
-
409
-        // Now validate all of the plugins
410
-        for (Map.Entry<String, PluginMetaData> target : newPluginsByPath.entrySet()) {
411
-            final PluginMetaDataValidator validator = new PluginMetaDataValidator(target.getValue());
412
-            final Collection<String> results = validator.validate(newPluginsByName, newServices);
413
-
414
-            if (results.isEmpty()) {
415
-                res.add(target.getValue());
416
-            } else {
417
-                eventBus.publish(new UserErrorEvent(ErrorLevel.MEDIUM, null,
418
-                        "Plugin validation failed for " + target.getKey() + ": " + results, ""));
419
-            }
420
-        }
421
-
422
-        return res;
355
+        return fileHandler.getKnownPlugins();
423 356
     }
424 357
 
425 358
     /**

+ 4
- 1
src/com/dmdirc/plugins/PluginModule.java View File

@@ -52,9 +52,12 @@ public class PluginModule {
52 52
             final ObjectGraph objectGraph,
53 53
             final ServiceManager serviceManager,
54 54
             final CorePluginHelper pluginHelper,
55
+            final PluginFileHandler fileHandler,
55 56
             @Directory(DirectoryType.PLUGINS) final String directory) {
56 57
         final PluginManager manager = new PluginManager(eventBus, serviceManager,
57
-                identityController, updateManager, objectGraph, directory);
58
+                identityController, updateManager, objectGraph, fileHandler, directory);
59
+        manager.refreshPlugins();
60
+
58 61
         final CorePluginExtractor extractor = new CorePluginExtractor(manager, directory, eventBus);
59 62
         pluginHelper.checkBundledPlugins(extractor, manager,
60 63
                 identityController.getGlobalConfiguration());

Loading…
Cancel
Save