Skip to content

Commit 053f679

Browse files
authored
fix: show locally installed games in drop-down when offline (#699)
* fix: show locally installed games in drop-down when offline * implement GameRelease.equals and hashCode based on id and timestamp.
1 parent f3c9f4a commit 053f679

File tree

2 files changed

+79
-22
lines changed

2 files changed

+79
-22
lines changed

src/main/java/org/terasology/launcher/model/GameRelease.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.net.URL;
77
import java.util.Date;
8+
import java.util.Objects;
89

910
/**
1011
* A game release describes a (remote) game artifact (asset) that can be downloaded and installed by the launcher.
@@ -50,4 +51,27 @@ public Date getTimestamp() {
5051
public String toString() {
5152
return id.getDisplayVersion();
5253
}
54+
55+
@Override
56+
public boolean equals(Object o) {
57+
if (o == this) {
58+
return true;
59+
}
60+
if (!(o instanceof GameRelease)) {
61+
return false;
62+
}
63+
GameRelease other = (GameRelease) o;
64+
65+
boolean sameId = this.id.equals(other.id);
66+
boolean sameTimestamp = (this.releaseMetadata == null && other.releaseMetadata == null)
67+
|| (this.releaseMetadata != null && other.releaseMetadata != null
68+
&& this.releaseMetadata.getTimestamp().equals(other.releaseMetadata.getTimestamp()));
69+
70+
return sameId && sameTimestamp;
71+
}
72+
73+
@Override
74+
public int hashCode() {
75+
return Objects.hash(id, releaseMetadata.getTimestamp());
76+
}
5377
}

src/main/java/org/terasology/launcher/ui/ApplicationController.java

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.terasology.launcher.model.GameIdentifier;
4242
import org.terasology.launcher.model.GameRelease;
4343
import org.terasology.launcher.model.Profile;
44+
import org.terasology.launcher.model.ReleaseMetadata;
4445
import org.terasology.launcher.repositories.RepositoryManager;
4546
import org.terasology.launcher.settings.Settings;
4647
import org.terasology.launcher.tasks.DeleteTask;
@@ -51,13 +52,16 @@
5152
import java.io.FileNotFoundException;
5253
import java.io.IOException;
5354
import java.nio.file.Path;
55+
import java.util.Date;
5456
import java.util.List;
5557
import java.util.Optional;
5658
import java.util.ResourceBundle;
59+
import java.util.Set;
5760
import java.util.concurrent.ExecutorService;
5861
import java.util.concurrent.Executors;
5962
import java.util.function.Predicate;
6063
import java.util.stream.Collectors;
64+
import java.util.stream.Stream;
6165

6266
public class ApplicationController {
6367

@@ -207,41 +211,70 @@ public void initialize() {
207211
* to the selected profile, and derive the currently selected release from the combo box's selection model.
208212
*/
209213
private void initComboBoxes() {
210-
// derive the releases to display from the selected profile (`selectedProfile`). the resulting list is ordered
211-
// in the way the launcher is supposed to display the versions (currently by release timestamp).
212-
config.addListener((obs, oldVal, cfg) -> {
213-
ObservableList<GameRelease> availableReleases = gameReleaseComboBox.getItems();
214-
GameIdentifier lastPlayedGame = cfg.getLauncherSettings().lastPlayedGameVersion.get();
215-
216-
Optional<GameRelease> lastPlayed = availableReleases.stream()
217-
.filter(release -> release.getId().equals(lastPlayedGame))
218-
.filter(release -> installedGames.contains(release.getId()))
219-
.findFirst();
220-
Optional<GameRelease> lastInstalled = availableReleases.stream()
221-
.filter(release -> installedGames.contains(release.getId()))
222-
.findFirst();
223-
224-
gameReleaseComboBox.getSelectionModel().select(lastPlayed
225-
.or(() -> lastInstalled)
226-
.or(() -> availableReleases.stream().findFirst())
227-
.orElse(null));
228-
});
229-
230214
final ObjectBinding<ObservableList<GameRelease>> releases = Bindings.createObjectBinding(() -> {
231215
LauncherConfiguration cfg = config.getValue();
232216
if (cfg == null || cfg.getRepositoryManager() == null) {
233217
return FXCollections.emptyObservableList();
234218
} else {
235219
RepositoryManager mngr = config.getValue().getRepositoryManager();
220+
221+
Set<GameRelease> onlineReleases = mngr.getReleases();
222+
// Create dummy game release objects from locally installed games.
223+
// We need this in case of running the launcher in "offline" mode
224+
// and the list of game releases fetched via the repository manager
225+
// is empty, but there are still games installed locally.
226+
//
227+
// However, we only want to add these dummy releases if they are
228+
// not listed in the online releases. Thus, filtering out everything
229+
// with a GameIdentifier that is already part of the releases fetched
230+
// from online sources.
231+
//
232+
//TODO: This is a weird place to create these dummy releases.
233+
// Move this code somewhere else, and make sure that we
234+
// have all the necessary information stored locally, like
235+
// the timestamp or the changelog.
236+
Set<GameIdentifier> onlineIds = onlineReleases.stream().map(GameRelease::getId).collect(Collectors.toSet());
237+
Stream<GameRelease> localGames = installedGames.stream()
238+
.filter(id -> !onlineIds.contains(id))
239+
.map(id -> new GameRelease(id, null, new ReleaseMetadata("", new Date())));
240+
241+
Stream<GameRelease> allReleases = Stream.concat(onlineReleases.stream(), localGames);
242+
236243
List<GameRelease> releasesForProfile =
237-
mngr.getReleases().stream()
244+
allReleases
238245
.filter(release -> release.getId().getProfile() == Profile.OMEGA)
239246
.filter(release -> showPreReleases.getValue() || release.getId().getBuild().equals(Build.STABLE))
240247
.sorted(ApplicationController::compareReleases)
241248
.collect(Collectors.toList());
249+
242250
return FXCollections.observableList(releasesForProfile);
243251
}
244-
}, config, showPreReleases);
252+
}, config, showPreReleases, installedGames);
253+
254+
// derive the releases to display from the selected profile (`selectedProfile`). the resulting list is ordered
255+
// in the way the launcher is supposed to display the versions (currently by release timestamp).
256+
final ObjectBinding<GameRelease> releaseToSelect = Bindings.createObjectBinding(()-> {
257+
GameIdentifier lastPlayedGame = Optional.ofNullable(config.getValue())
258+
.map(cfg -> cfg.getLauncherSettings().lastPlayedGameVersion.get())
259+
.orElse(null);
260+
261+
Optional<GameRelease> lastPlayed = releases.get().stream()
262+
.filter(release -> release.getId().equals(lastPlayedGame))
263+
.filter(release -> installedGames.contains(release.getId()))
264+
.findFirst();
265+
Optional<GameRelease> lastInstalled = releases.get().stream()
266+
.filter(release -> installedGames.contains(release.getId()))
267+
.findFirst();
268+
269+
return lastPlayed
270+
.or(() -> lastInstalled)
271+
.or(() -> releases.get().stream().findFirst())
272+
.orElse(null);
273+
}, releases, config);
274+
275+
releaseToSelect.addListener((obs, old, now) -> {
276+
gameReleaseComboBox.getSelectionModel().select(now);
277+
});
245278

246279
gameReleaseComboBox.itemsProperty().bind(releases);
247280
gameReleaseComboBox.buttonCellProperty()

0 commit comments

Comments
 (0)