@@ -263,4 +263,90 @@ public List<String> listManuallyInstalled() {
263
263
264
264
return Stream .concat (inLock , noLock ).toList ();
265
265
}
266
+
267
+ /**
268
+ * List installed plugins in an index map
269
+ *
270
+ * @return Index map (dict[name: plugin])
271
+ */
272
+ private Map <String , PluginYml > mapInstalled () {
273
+ return listInstalled ().stream ()
274
+ .map (it -> new Pair <>(it .name (), it ))
275
+ .collect (Pair .toMap ());
276
+ }
277
+
278
+ /**
279
+ * Get a list of automatically installed plugin dependencies that are no longer required
280
+ *
281
+ * @param considerSoftDependencies Whether to preserve soft dependencies (should be false)
282
+ * @return List of plugins
283
+ */
284
+ @ Override
285
+ public List <PluginYml > listOrphanPlugins (boolean considerSoftDependencies ) {
286
+ Map <String , PluginYml > installed = mapInstalled ();
287
+ List <String > manual = listManuallyInstalled ();
288
+ List <String > deps = new ArrayList <>();
289
+
290
+ // Get all the dependencies of the manually installed plugins
291
+ for (String name : manual ) {
292
+
293
+ // If the plugin is not in the local installed folder, skip it
294
+ if (!installed .containsKey (name )) continue ;
295
+
296
+ // Read plugin yml
297
+ var p = installed .get (name );
298
+
299
+ // Add the dependencies of the plugin to the list of required dependencies
300
+ if (p .depend () != null )
301
+ deps .addAll (p .depend ());
302
+
303
+ // If considerSoftDependencies is true, add the soft dependencies to the list of
304
+ // required dependencies
305
+ if (p .softdepend () != null && considerSoftDependencies )
306
+ deps .addAll (p .softdepend ());
307
+ }
308
+
309
+ // Get the difference between the set of manually installed plugins,
310
+ // the set of required dependencies, and the set of all installed plugins.
311
+ return installed .values ().stream ()
312
+ .filter (it -> !manual .contains (it .name ()) && !deps .contains (it .name ())).toList ();
313
+ }
314
+
315
+ /**
316
+ * Get a list of plugin (as pluginYml) that are outdated
317
+ *
318
+ * @return List of plugin names
319
+ */
320
+ @ Override
321
+ public List <PluginVersion > listOutdatedPlugins (Database database ) {
322
+ // Database plugins' latest versions
323
+ var versions = database .plugins ().stream ().map (PluginModel ::getLatestPluginVersion )
324
+ .filter (Optional ::isPresent ).map (Optional ::get ).toList ();
325
+
326
+ // Database index map by plugin ID
327
+ var dbIdIndex = versions .stream ().map (it -> new Pair <>(it .id (), it )).collect (Pair .toMap ());
328
+
329
+ // Database index map by fuzzy identifier ("{name},{main}")
330
+ var dbFuzzyIndex = versions .stream ()
331
+ .map (it -> new Pair <>(String .format ("%s,%s" , it .meta ().name (), it .meta ().main ()), it ))
332
+ .collect (Pair .toMap ());
333
+
334
+ var installed = listInstalled ();
335
+ var entries = mapLock ();
336
+
337
+ // For plugins that have plugin id and version ids recorded:
338
+ var outdatedEntries = entries .values ().stream ().filter (it -> dbIdIndex .containsKey (it .getPluginId ()))
339
+ .map (it -> new Pair <>(it , dbIdIndex .get (it .getPluginId ())))
340
+ .filter (it -> it .v () != null && it .v ().id () > it .k ().getVersionId ())
341
+ .map (Pair ::v );
342
+
343
+ // For plugins that are installed through other means that don't have a plugin id recorded.
344
+ // It requires an exact match of the plugin name + plugin main class
345
+ var outdatedFuzzy = installed .stream ().filter (it -> !entries .containsKey (it .name ()))
346
+ .map (it -> String .format ("%s,%s" , it .name (), it .main ()))
347
+ .filter (dbFuzzyIndex ::containsKey )
348
+ .map (dbFuzzyIndex ::get );
349
+
350
+ return Stream .concat (outdatedFuzzy , outdatedEntries ).toList ();
351
+ }
266
352
}
0 commit comments