Fix #3065: 修复 CurseForge 搜索 API 对翻页总量计算不正确的 Bug (#3066)

* Fix #3065

* Fix

* Fix.
This commit is contained in:
Burning_TNT
2024-07-20 05:03:08 +08:00
committed by GitHub
parent 7c7c36f8aa
commit 61096142d5
3 changed files with 82 additions and 44 deletions

View File

@@ -43,7 +43,6 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
@@ -52,6 +51,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.AggregatedObservableList;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
@@ -64,10 +64,10 @@ import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
@@ -87,7 +87,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList());
protected final StringProperty downloadSource = new SimpleStringProperty();
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
private TaskExecutor executor;
private int searchID = 0;
protected RemoteModRepository repository;
private Runnable retrySearch;
@@ -163,11 +163,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
setLoading(true);
setFailed(false);
if (executor != null && !executor.isCancelled()) {
executor.cancel();
}
executor = Task.supplyAsync(() -> {
int currentSearchID = searchID = searchID + 1;
Task.supplyAsync(() -> {
Profile.ProfileVersion version = this.version.get();
if (StringUtils.isBlank(version.getVersion())) {
return userGameVersion;
@@ -176,9 +173,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
: "";
}
}).thenApplyAsync(gameVersion ->
repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
).whenComplete(Schedulers.javafx(), (result, exception) -> {
}).thenApplyAsync(gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (searchID != currentSearchID) {
return;
}
setLoading(false);
if (exception == null) {
items.setAll(result.getResults().collect(Collectors.toList()));
@@ -196,7 +195,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
return i18n("curse.category." + category);
}
protected String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
return StringUtils.repeats(' ', category.indent * 4) +
(category.getCategory() == null
? i18n("curse.category.0")
@@ -344,13 +343,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
sortComboBox.getSelectionModel().select(0);
searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane);
StringProperty previousSearchFilter = new SimpleStringProperty(this, "Previous Seach Filter", "");
IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0);
IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1);
EventHandler<ActionEvent> searchAction = e -> {
if (!previousSearchFilter.get().equals(nameField.getText())) {
if (currentFilterID.get() != filterID.get()) {
control.pageOffset.set(0);
}
currentFilterID.set(filterID.get());
previousSearchFilter.set(nameField.getText());
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
.map(CategoryIndented::getCategory)
@@ -360,60 +360,93 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
sortComboBox.getSelectionModel().getSelectedItem());
};
control.listenerHolder.add(FXUtils.observeWeak(
() -> filterID.set(filterID.get() + 1),
control.downloadSource,
gameVersionField.getSelectionModel().selectedItemProperty(),
categoryComboBox.getSelectionModel().selectedItemProperty(),
nameField.textProperty(),
sortComboBox.getSelectionModel().selectedItemProperty()
));
HBox actionsBox = new HBox(8);
GridPane.setColumnSpan(actionsBox, 4);
actionsBox.setAlignment(Pos.CENTER);
{
AggregatedObservableList<Node> actions = new AggregatedObservableList<>();
Holder<Runnable> changeButton = new Holder<>();
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
firstPageButton.setOnAction(event -> {
control.pageOffset.set(0);
changeButton.value.run();
searchAction.handle(event);
});
firstPageButton.setDisable(true);
control.pageCount.addListener((observable, oldValue, newValue) -> firstPageButton.setDisable(control.pageCount.get() == -1));
JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page"));
previousPageButton.setOnAction(event -> {
if (control.pageOffset.get() > 0) {
control.pageOffset.set(control.pageOffset.get() - 1);
int pageOffset = control.pageOffset.get();
if (pageOffset > 0) {
control.pageOffset.set(pageOffset - 1);
changeButton.value.run();
searchAction.handle(event);
}
});
previousPageButton.setDisable(true);
control.pageOffset.addListener((observable, oldValue, newValue) -> previousPageButton.setDisable(
control.pageCount.get() == -1 || control.pageOffset.get() == 0
));
Label pageOffset = new Label(i18n("search.page_n", 0, "-"));
control.pageOffset.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
)));
control.pageCount.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
)));
Label pageDescription = new Label();
pageDescription.textProperty().bind(Bindings.createStringBinding(() -> {
int pageCount = control.pageCount.get();
return i18n("search.page_n", control.pageOffset.get() + 1, pageCount == -1 ? "-" : String.valueOf(pageCount));
}, control.pageOffset, control.pageCount));
JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page"));
nextPageButton.setOnAction(event -> {
control.pageOffset.set(control.pageOffset.get() + 1);
searchAction.handle(event);
int nv = control.pageOffset.get() + 1;
if (nv < control.pageCount.get()) {
control.pageOffset.set(nv);
changeButton.value.run();
searchAction.handle(event);
}
});
nextPageButton.setDisable(true);
control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable(
control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1
));
control.pageCount.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable(
control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1
));
JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page"));
lastPageButton.setOnAction(event -> {
control.pageOffset.set(control.pageCount.get() - 1);
changeButton.value.run();
searchAction.handle(event);
});
firstPageButton.setDisable(true);
previousPageButton.setDisable(true);
lastPageButton.setDisable(true);
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1));
nextPageButton.setDisable(true);
changeButton.value = () -> {
int pageOffset = control.pageOffset.get();
int pageCount = control.pageCount.get();
boolean disablePrevious = pageOffset == 0;
firstPageButton.setDisable(disablePrevious);
previousPageButton.setDisable(disablePrevious);
boolean disableNext = pageOffset == pageCount - 1;
nextPageButton.setDisable(disableNext);
lastPageButton.setDisable(disableNext || pageCount == -1);
};
FXUtils.onChange(control.pageCount, pageCountN -> {
int pageCount = pageCountN.intValue();
if (pageCount != -1) {
if (control.pageOffset.get() + 1 >= pageCount) {
control.pageOffset.set(pageCount - 1);
}
}
changeButton.value.run();
});
Pane placeholder = new Pane();
HBox.setHgrow(placeholder, Priority.SOMETIMES);
@@ -421,13 +454,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
JFXButton searchButton = FXUtils.newRaisedButton(i18n("search"));
searchButton.setOnAction(searchAction);
actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageOffset, nextPageButton, lastPageButton, placeholder, searchButton));
actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageDescription, nextPageButton, lastPageButton, placeholder, searchButton));
actions.appendList(control.actions);
Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList());
}
searchPane.addRow(rowIndex++, actionsBox);
FXUtils.onChange(control.downloadSource, v -> searchAction.handle(null));
nameField.setOnAction(searchAction);
gameVersionField.setOnAction(searchAction);
categoryComboBox.setOnAction(searchAction);

View File

@@ -94,6 +94,10 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
return "asc";
}
private int calculateTotalPages(Response<List<CurseAddon>> response, int pageSize) {
return (int) Math.ceil((double) Math.min(response.pagination.totalCount, 10000) / pageSize);
}
@Override
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
int categoryId = 0;
@@ -112,7 +116,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
}.getType());
if (searchFilter.isEmpty()) {
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), (int)Math.ceil((double)response.pagination.totalCount / pageSize));
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
}
// https://github.com/HMCL-dev/HMCL/issues/1549
@@ -135,7 +139,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
}
return pair(remoteMod, diff);
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), response.pagination.totalCount);
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
}
@Override

View File

@@ -95,7 +95,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
.getJson(new TypeToken<Response<ProjectSearchResult>>() {
}.getType());
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int)Math.ceil((double)response.totalHits / pageSize));
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
}
@Override