Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 42 additions & 9 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -109,6 +110,8 @@ public record InstanceReference(HMCLGameRepository repository, @Nullable GameIns
private final Set<GameInstanceID> loadedInstanceGameSettings = new HashSet<>();
private final Set<GameInstanceID> readOnlyInstanceGameSettings = new HashSet<>();
private final Set<GameInstanceID> beingModpackInstances = new HashSet<>();
/// Instance IDs whose instance game settings must not be written back to disk
private final Set<GameInstanceID> beingRemovedInstances = ConcurrentHashMap.newKeySet();

public final EventManager<Event> onInstanceIconChanged = new EventManager<>();

Expand Down Expand Up @@ -243,17 +246,43 @@ public void clean(GameInstanceID instanceId) throws IOException {
clean(getRunDirectory(instanceId));
}

/// Marks the instance as being removed so that instance game settings changes
/// are not written back to disk
public void markInstanceBeingRemoved(GameInstanceID instanceId) {
beingRemovedInstances.add(instanceId);
}

/// Unmarks the instance so that instance game settings can be saved again,
/// e.g. when the removal failed.
public void unmarkInstanceBeingRemoved(GameInstanceID instanceId) {
beingRemovedInstances.remove(instanceId);
}

/// Removes an instance from disk and clears its cached HMCL settings state.
@Override
public boolean removeInstanceFromDisk(GameInstanceID instanceId) {
boolean removed = super.removeInstanceFromDisk(instanceId);
if (removed) {
instanceGameSettings.remove(instanceId);
loadedInstanceGameSettings.remove(instanceId);
readOnlyInstanceGameSettings.remove(instanceId);
beingModpackInstances.remove(instanceId);
beingRemovedInstances.add(instanceId);
try {
// Flush settings saves that were enqueued before the removal started, so that they
// cannot recreate the instance directory after it has been moved
try {
FileSaver.waitForAllSaves();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warning("Interrupted while waiting for pending file saves", e);
}

boolean removed = super.removeInstanceFromDisk(instanceId);
if (removed) {
instanceGameSettings.remove(instanceId);
loadedInstanceGameSettings.remove(instanceId);
readOnlyInstanceGameSettings.remove(instanceId);
beingModpackInstances.remove(instanceId);
}
return removed;
} finally {
beingRemovedInstances.remove(instanceId);
}
return removed;
}

public void duplicateInstance(GameInstanceID srcId, GameInstanceID dstId, boolean copySaves) throws IOException {
Expand Down Expand Up @@ -680,7 +709,9 @@ else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE))
}

public void saveGameSettings(GameInstanceID instanceId) {
if (!instanceGameSettings.containsKey(instanceId) || readOnlyInstanceGameSettings.contains(instanceId))
if (beingRemovedInstances.contains(instanceId)
|| !instanceGameSettings.containsKey(instanceId)
|| readOnlyInstanceGameSettings.contains(instanceId))
return;
GameSettings.Instance setting = instanceGameSettings.get(instanceId);
if (setting == null) {
Expand All @@ -705,7 +736,9 @@ public void saveGameSettings(GameInstanceID instanceId) {
/// @param instanceId the instance ID
/// @throws IOException if saving the file fails
private void saveGameSettingsSync(GameInstanceID instanceId) throws IOException {
if (!instanceGameSettings.containsKey(instanceId) || readOnlyInstanceGameSettings.contains(instanceId)) {
if (beingRemovedInstances.contains(instanceId)
|| !instanceGameSettings.containsKey(instanceId)
|| readOnlyInstanceGameSettings.contains(instanceId)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,13 @@ public static void deleteInstance(HMCLGameRepository repository, GameInstanceID
JFXButton deleteButton = new JFXButton(i18n("button.delete"));
deleteButton.getStyleClass().add("dialog-error");
deleteButton.setOnAction(e -> {
// Block instance game settings from being written back to disk while the deletion
// task is running, otherwise a settings change during the deletion may recreate
// the instance directory after it has been moved to the trash
repository.markInstanceBeingRemoved(instanceId);
Task.supplyAsync(Schedulers.io(), () -> repository.removeInstanceFromDisk(instanceId))
.whenComplete(Schedulers.javafx(), (result, exception) -> {
repository.unmarkInstanceBeingRemoved(instanceId);
if (exception != null || !Boolean.TRUE.equals(result)) {
Controllers.dialog(i18n("instance.manage.remove.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
}
Expand Down