Skip to content

Commit 03449f8

Browse files
committed
merge
2 parents 95535e8 + bcfdbc6 commit 03449f8

27 files changed

+754
-555
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ build
3131
data
3232
.mcpm
3333
*.pyc
34+
35+
plugins

src/main/java/org/hydev/mcpm/client/arguments/CommandsFactory.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
import org.hydev.mcpm.client.Downloader;
66
import org.hydev.mcpm.client.arguments.parsers.*;
77
import org.hydev.mcpm.client.commands.entries.*;
8+
import org.hydev.mcpm.client.database.CheckForUpdatesInteractor;
89
import org.hydev.mcpm.client.database.ListAllInteractor;
910
import org.hydev.mcpm.client.database.LocalPluginTracker;
11+
import org.hydev.mcpm.client.database.MatchPluginsInteractor;
12+
import org.hydev.mcpm.client.database.fetcher.ProgressBarFetcherListener;
1013
import org.hydev.mcpm.client.database.mirrors.MirrorSelector;
1114
import org.hydev.mcpm.client.database.export.ExportInteractor;
1215
import org.hydev.mcpm.client.database.fetcher.LocalDatabaseFetcher;
1316
import org.hydev.mcpm.client.database.searchusecase.SearchInteractor;
1417
import org.hydev.mcpm.client.injector.PluginLoader;
1518
import org.hydev.mcpm.client.installer.InstallInteractor;
1619
import org.hydev.mcpm.client.installer.SpigotPluginDownloader;
20+
import org.hydev.mcpm.client.updater.UpdateInteractor;
1721
import org.hydev.mcpm.utils.ColorLogger;
1822

1923
import java.net.URI;
@@ -40,22 +44,25 @@ public static List<CommandParser> baseParsers(boolean isMinecraft) {
4044
var host = URI.create("https://mcpm.hydev.org");
4145
var fetcher = new LocalDatabaseFetcher(host);
4246
var tracker = new LocalPluginTracker();
43-
var searcher = new SearchInteractor(fetcher);
47+
var loader = isMinecraft ? new PluginLoader() : null;
48+
var listener = new ProgressBarFetcherListener();
49+
var searcher = new SearchInteractor(fetcher, listener);
50+
var installer = new InstallInteractor(
51+
new SpigotPluginDownloader(new Downloader(), host.toString()),
52+
new DatabaseManager(tracker, searcher),
53+
loader
54+
);
55+
var matcher = new MatchPluginsInteractor(fetcher, listener);
56+
var updateChecker = new CheckForUpdatesInteractor(matcher);
57+
var updater = new UpdateInteractor(updateChecker, installer, tracker);
58+
4459
var exportPluginsController = new ExportPluginsController(new ExportInteractor(tracker));
4560
var listController = new ListController(new ListAllInteractor(tracker));
4661
var searchController = new SearchPackagesController(searcher);
4762
var mirrorController = new MirrorController(new MirrorSelector());
4863
var infoController = new InfoController(tracker);
49-
PluginLoader pluginLoader = null;
50-
if (isMinecraft) {
51-
pluginLoader = new PluginLoader();
52-
}
53-
DatabaseManager databaseManager = new DatabaseManager(tracker, searcher);
54-
System.out.println(isMinecraft);
55-
var installController = new InstallController(new InstallInteractor(
56-
new SpigotPluginDownloader(new Downloader(), host.toString()),
57-
databaseManager, pluginLoader));
58-
64+
var installController = new InstallController(installer);
65+
var updateController = new UpdateController(updater);
5966

6067
/*
6168
* Add general parsers to this list!
@@ -68,7 +75,8 @@ public static List<CommandParser> baseParsers(boolean isMinecraft) {
6875
new SearchParser(searchController),
6976
new MirrorParser(mirrorController),
7077
new InfoParser(infoController),
71-
new InstallParser(installController)
78+
new InstallParser(installController),
79+
new UpdateParser(updateController)
7280
);
7381
}
7482

src/main/java/org/hydev/mcpm/client/arguments/parsers/MirrorParser.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public void run(Namespace details, Consumer<String> log)
3535
{
3636
case "ping" -> controller.ping(details.getBoolean("refresh"), log);
3737
case "select" -> controller.select(details.getString("host"), log);
38+
default -> throw new RuntimeException();
3839
}
3940
}
4041

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.hydev.mcpm.client.arguments.parsers;
2+
3+
import net.sourceforge.argparse4j.impl.Arguments;
4+
import net.sourceforge.argparse4j.inf.Namespace;
5+
import net.sourceforge.argparse4j.inf.Subparser;
6+
import org.hydev.mcpm.client.commands.entries.UpdateController;
7+
import org.hydev.mcpm.client.commands.presenters.LogUpdatePresenter;
8+
9+
import java.util.function.Consumer;
10+
11+
/**
12+
* Handles parsing related to the update command.
13+
*
14+
* @param controller A controller to dispatch an update command when invoked.
15+
*/
16+
public record UpdateParser(UpdateController controller) implements CommandParser {
17+
@Override
18+
public String name() {
19+
return "update";
20+
}
21+
22+
@Override
23+
public String description() {
24+
return "Updates plugins to the latest version.";
25+
}
26+
27+
@Override
28+
public void configure(Subparser parser) {
29+
// if (Constants.IS_MINECRAFT) {
30+
parser.addArgument("--load")
31+
.type(boolean.class)
32+
.action(Arguments.storeTrue())
33+
.dest("load")
34+
.help("If true, updated plugins will be reloaded after the update.");
35+
// }
36+
37+
parser.addArgument("--no-cache")
38+
.type(boolean.class)
39+
.action(Arguments.storeTrue())
40+
.dest("no-cache")
41+
.help("If true, the cache will be skipped and database will be fetched again.");
42+
43+
parser.addArgument("names")
44+
.nargs("*")
45+
.help("List of plugin names to update.");
46+
}
47+
48+
@Override
49+
public void run(Namespace details, Consumer<String> log) {
50+
// Since log can change from invocation to invocation,
51+
// and I don't want UpdatePresenter to depend on Consumer<String>,
52+
// I'll instantiate this every call.
53+
var presenter = new LogUpdatePresenter(log);
54+
55+
controller.update(
56+
details.getList("names"),
57+
details.getBoolean("load"),
58+
details.getBoolean("no-cache"),
59+
presenter
60+
);
61+
}
62+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.hydev.mcpm.client.commands.entries;
2+
3+
import org.hydev.mcpm.client.commands.presenters.UpdatePresenter;
4+
import org.hydev.mcpm.client.updater.UpdateBoundary;
5+
import org.hydev.mcpm.client.updater.UpdateInput;
6+
7+
import java.util.List;
8+
9+
/**
10+
* Controller for the update command.
11+
*
12+
* @param boundary Update requests are dispatched to this boundary.
13+
*/
14+
public record UpdateController(UpdateBoundary boundary) {
15+
/**
16+
* Invoke this to dispatch an update request to the boundary.
17+
*
18+
* @param names A list of names to update.
19+
* @param load Whether to reload installed plugins (ignored on CLI environment).
20+
* @param noCache Whether to force fetch the database before updating.
21+
* @param presenter A presenter object to format the update boundary result.
22+
*/
23+
public void update(List<String> names, boolean load, boolean noCache, UpdatePresenter presenter) {
24+
var input = new UpdateInput(names, load, noCache);
25+
var result = boundary.update(input);
26+
27+
// This is being done in the Controller for this time being.
28+
// Rena suggested that we move this down into the boundary, I am okay either way.
29+
presenter.present(input, result);
30+
}
31+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package org.hydev.mcpm.client.commands.presenters;
2+
3+
import org.hydev.mcpm.client.updater.UpdateInput;
4+
import org.hydev.mcpm.client.updater.UpdateOutcome;
5+
import org.hydev.mcpm.client.updater.UpdateResult;
6+
import org.hydev.mcpm.utils.FormatUtils;
7+
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Optional;
11+
import java.util.function.Consumer;
12+
import java.util.stream.Collectors;
13+
14+
/**
15+
* Formats update information for the CLI and writes it to log.
16+
*
17+
* @param log A consumer that takes Minecraft Color coded strings to print to the user.
18+
*/
19+
public record LogUpdatePresenter(Consumer<String> log) implements UpdatePresenter {
20+
private static String userShortFromType(UpdateOutcome.State state) {
21+
return switch (state) {
22+
case MISMATCHED -> "Not Found";
23+
case NOT_INSTALLED -> "Not Installed";
24+
case NETWORK_ERROR -> "Network Error";
25+
case UP_TO_DATE -> "Up to Date";
26+
case UPDATED -> "Updated";
27+
};
28+
}
29+
30+
private static String colorFromType(UpdateOutcome.State state) {
31+
return switch (state) {
32+
case MISMATCHED, NOT_INSTALLED, NETWORK_ERROR -> "&c";
33+
case UP_TO_DATE -> "&7";
34+
case UPDATED -> "&2";
35+
};
36+
}
37+
38+
private static String versionStringFromOutcome(UpdateOutcome outcome) {
39+
var initial = Optional.ofNullable(outcome.initialVersion());
40+
var destination = Optional.ofNullable(outcome.destinationVersion());
41+
42+
// I don't really care how these are presented, but I want to handle the case.
43+
// Having the user see ? in a case where we don't have a plugin version seem sane.
44+
var initialString = initial.orElse("?");
45+
var destinationString = destination.orElse("?");
46+
47+
return switch (outcome.state()) {
48+
case MISMATCHED -> "N/A";
49+
case NOT_INSTALLED, NETWORK_ERROR, UP_TO_DATE -> initialString;
50+
case UPDATED -> initialString + " -> " + destinationString;
51+
};
52+
}
53+
54+
private static List<String> flattenOutcome(Map.Entry<String, UpdateOutcome> outcome) {
55+
var color = colorFromType(outcome.getValue().state());
56+
57+
return List.of(
58+
color + outcome.getKey(),
59+
color + userShortFromType(outcome.getValue().state()),
60+
color + versionStringFromOutcome(outcome.getValue())
61+
);
62+
}
63+
64+
private static String tabulateOutcomes(Map<String, UpdateOutcome> outcomes) {
65+
var headers = List.of(":Name", ":Status", "Version:");
66+
67+
var rows = outcomes.entrySet().stream()
68+
.sorted(Map.Entry.comparingByKey())
69+
.map(LogUpdatePresenter::flattenOutcome)
70+
.toList();
71+
72+
return FormatUtils.tabulate(rows, headers);
73+
}
74+
75+
// Unsure if this should take the input, but it allows for a nicer formatting.
76+
@Override
77+
public void present(UpdateInput input, UpdateResult result) {
78+
var outcomes = result.outcomes();
79+
80+
// If we are updating all plugins, only show users plugins that were upgraded.
81+
if (input.updateAll()) {
82+
outcomes = outcomes.entrySet().stream()
83+
.filter(x -> x.getValue().state() != UpdateOutcome.State.UP_TO_DATE)
84+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
85+
}
86+
87+
if (outcomes.isEmpty()) {
88+
log.accept("&2All plugins are up to date.");
89+
} else {
90+
var updated = outcomes.values().stream()
91+
.filter(x -> x.state() == UpdateOutcome.State.UPDATED)
92+
.count();
93+
94+
var failed = outcomes.values().stream()
95+
.filter(x -> !x.state().success())
96+
.count();
97+
98+
if (failed > 0) {
99+
log.accept("&cFailed to update " + failed + " plugins (" + updated + " plugins updated).");
100+
} else if (updated > 0) {
101+
log.accept("&2Updated " + updated + " plugins successfully.");
102+
} else {
103+
log.accept("&2All plugins are up to date.");
104+
}
105+
106+
log.accept(tabulateOutcomes(outcomes));
107+
}
108+
}
109+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.hydev.mcpm.client.commands.presenters;
2+
3+
import org.hydev.mcpm.client.updater.UpdateInput;
4+
import org.hydev.mcpm.client.updater.UpdateResult;
5+
6+
/**
7+
* Interface for presenters to implement who can format update result objects.
8+
*/
9+
public interface UpdatePresenter {
10+
/**
11+
* Formats and displays an UpdateResult object.
12+
* This also takes an input object for a more detailed formatting.
13+
*
14+
* @param input The input object that dispatched the update request.
15+
* @param result The returned result object that contains update outcomes.
16+
*/
17+
void present(UpdateInput input, UpdateResult result);
18+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.hydev.mcpm.client.database;
2+
3+
import org.hydev.mcpm.client.database.boundary.CheckForUpdatesBoundary;
4+
import org.hydev.mcpm.client.database.boundary.MatchPluginsBoundary;
5+
import org.hydev.mcpm.client.database.inputs.CheckForUpdatesInput;
6+
import org.hydev.mcpm.client.database.inputs.MatchPluginsInput;
7+
import org.hydev.mcpm.client.database.model.PluginVersionState;
8+
import org.hydev.mcpm.client.database.results.CheckForUpdatesResult;
9+
import org.hydev.mcpm.client.database.results.MatchPluginsResult;
10+
import org.hydev.mcpm.client.models.PluginModel;
11+
12+
import java.util.HashMap;
13+
import java.util.Set;
14+
import java.util.stream.Collectors;
15+
16+
/**
17+
* Checks for updates from a MatchBoundary object.
18+
*
19+
* @param matchBoundary A match boundary that wraps Database information.
20+
*/
21+
public record CheckForUpdatesInteractor(
22+
MatchPluginsBoundary matchBoundary
23+
) implements CheckForUpdatesBoundary {
24+
/**
25+
* Checks for plugin updates.
26+
*
27+
* @param forInput A list of all plugins + plugin version identifiers that will be checked for updates.
28+
* @return A list of plugins that needs updates in CheckForUpdatesResult#updates.
29+
*/
30+
@Override
31+
public CheckForUpdatesResult updates(CheckForUpdatesInput forInput) {
32+
var isVersionsValid = forInput.states().stream()
33+
.allMatch(state -> state.versionId().valid());
34+
35+
if (!isVersionsValid) {
36+
return CheckForUpdatesResult.by(CheckForUpdatesResult.State.INVALID_INPUT);
37+
}
38+
39+
var matchInput = new MatchPluginsInput(
40+
forInput.states().stream().map(PluginVersionState::modelId).toList(),
41+
forInput.noCache()
42+
);
43+
44+
var result = matchBoundary.match(matchInput);
45+
46+
return switch (result.state()) {
47+
case SUCCESS ->
48+
filterUpdatablePlugins(forInput, result);
49+
50+
case INVALID_INPUT ->
51+
CheckForUpdatesResult.by(CheckForUpdatesResult.State.INVALID_INPUT);
52+
53+
case FAILED_TO_FETCH_DATABASE ->
54+
CheckForUpdatesResult.by(CheckForUpdatesResult.State.FAILED_TO_FETCH_DATABASE);
55+
};
56+
}
57+
58+
private static CheckForUpdatesResult filterUpdatablePlugins(CheckForUpdatesInput forInput,
59+
MatchPluginsResult result) {
60+
var mismatchedSet = Set.copyOf(result.mismatched());
61+
var mismatched = forInput.states().stream()
62+
.filter(state -> mismatchedSet.contains(state.modelId()))
63+
.collect(Collectors.toSet());
64+
65+
var updatable = new HashMap<PluginVersionState, PluginModel>();
66+
67+
for (var state : forInput.states()) {
68+
var value = result.matched().getOrDefault(state.modelId(), null);
69+
70+
if (value == null) {
71+
continue;
72+
}
73+
74+
var optionalLatest = value.getLatestPluginVersion();
75+
76+
// guard let?
77+
if (optionalLatest.isEmpty()) {
78+
continue;
79+
}
80+
81+
var latest = optionalLatest.get();
82+
83+
if (!state.versionId().matches(latest)) {
84+
updatable.put(state, value);
85+
}
86+
}
87+
88+
return new CheckForUpdatesResult(CheckForUpdatesResult.State.SUCCESS, updatable, mismatched);
89+
}
90+
}

0 commit comments

Comments
 (0)