Skip to content

Commit

Permalink
Allow to shortcut search to show already received results
Browse files Browse the repository at this point in the history
Closes #914
  • Loading branch information
theotherp committed Jan 21, 2024
1 parent 3acb2ce commit 5cec4be
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 19 deletions.
8 changes: 8 additions & 0 deletions core/src/main/java/org/nzbhydra/searching/SearchWeb.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.http.MediaType;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
Expand Down Expand Up @@ -82,6 +83,13 @@ public SearchResponse search(@RequestBody SearchRequestParameters parameters) {
return searchResponse;
}

@Secured({"ROLE_USER"})
@RequestMapping(value = "/internalapi/shortcutSearch/{searchRequestId}", method = RequestMethod.POST)
public void shortcutSearch(@PathVariable Long searchRequestId) {
logger.info("Requested shortcut of search with ID {}", searchRequestId);
searcher.shortcutSearch(searchRequestId);
}


private void sendSearchState(SearchState searchState) {
messagingTemplate.convertAndSend("/topic/searchState", searchState);
Expand Down
45 changes: 37 additions & 8 deletions core/src/main/java/org/nzbhydra/searching/Searcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -71,6 +72,11 @@ public class Searcher {
@Autowired
private ConfigProvider configProvider;
private final Set<ExecutorService> executors = Collections.synchronizedSet(new HashSet<>());
private final Map<Long, List<Future<IndexerSearchResult>>> searchCallables = ExpiringMap.builder()
.maxSize(10)
.expiration(5, TimeUnit.MINUTES) //This should be more than enough... Nobody will wait that long
.expirationPolicy(ExpirationPolicy.ACCESSED)
.build();
private boolean shutdownRequested = false;

/**
Expand All @@ -97,7 +103,7 @@ public SearchResult search(SearchRequest searchRequest) {
List<IndexerSearchCacheEntry> indexersToSearch = getIndexersToSearch(searchCacheEntry);
List<IndexerSearchCacheEntry> indexersWithCachedResults = getIndexersWithCachedResults(searchCacheEntry);
List<SearchResultItem> searchResultItems = searchCacheEntry.getSearchResultItems();
while ((!indexersToSearch.isEmpty() || !indexersWithCachedResults.isEmpty()) && (searchResultItems.size() < numberOfWantedResults || searchRequest.isLoadAll())) {
while ((!indexersToSearch.isEmpty() || !indexersWithCachedResults.isEmpty()) && (searchResultItems.size() < numberOfWantedResults || searchRequest.isLoadAll()) && !searchRequest.isShortcut()) {
if (shutdownRequested) {
break;
}
Expand Down Expand Up @@ -355,17 +361,27 @@ protected void callSearchModules(SearchRequest searchRequest, List<IndexerSearch
ExecutorService executor = MdcThreadPoolExecutor.newWithInheritedMdc(indexersToSearch.size());
executors.add(executor);

List<Callable<IndexerSearchResult>> callables = getCallables(searchRequest, indexersToSearch);
List<IndexerCallable> callables = getRegisteredCallables(searchRequest, indexersToSearch);

try {
List<Future<IndexerSearchResult>> futures = executor.invokeAll(callables);
List<Future<IndexerSearchResult>> futures = new ArrayList<>();
for (IndexerCallable callable : callables) {
Future<IndexerSearchResult> future = executor.submit(callable.callable());
// searchCallables.put(new SearchFutureEntry(searchRequest.getSearchRequestId(), callable.indexerName()), future);
futures.add(future);
}
searchCallables.put(searchRequest.getSearchRequestId(), futures);

for (Future<IndexerSearchResult> future : futures) {
try {
IndexerSearchResult indexerSearchResult = future.get();
searchCacheEntry.getIndexerCacheEntries().get(indexerSearchResult.getIndexer().getName()).addIndexerSearchResult(indexerSearchResult);
indexerSearchResults.put(indexerSearchResult.getIndexer(), searchCacheEntry.getIndexerCacheEntries().get(indexerSearchResult.getIndexer().getName()).getIndexerSearchResults());
} catch (ExecutionException e) {
logger.error("Unexpected error while searching", e);
} catch (CancellationException e) {
logger.debug("Cancellation of call expected");
searchRequest.setShortcut(true);
}
}
} catch (InterruptedException e) {
Expand All @@ -377,6 +393,12 @@ protected void callSearchModules(SearchRequest searchRequest, List<IndexerSearch
handleIndexersWithFailedFutureExecutions(indexersToSearch, indexerSearchResults);
}

public void shortcutSearch(Long searchRequestId) {
for (Future<IndexerSearchResult> x : searchCallables.get(searchRequestId)) {
x.cancel(true);
}
}


private void handleIndexersWithFailedFutureExecutions(List<IndexerSearchCacheEntry> indexerSearchCacheEntries, Map<Indexer, List<IndexerSearchResult>> indexerSearchResults) {
for (IndexerSearchCacheEntry toSearch : indexerSearchCacheEntries) {
Expand All @@ -392,18 +414,18 @@ private void handleIndexersWithFailedFutureExecutions(List<IndexerSearchCacheEnt
}
}

private List<Callable<IndexerSearchResult>> getCallables(SearchRequest searchRequest, List<IndexerSearchCacheEntry> indexersToSearch) {
List<Callable<IndexerSearchResult>> callables = new ArrayList<>();
private List<IndexerCallable> getRegisteredCallables(SearchRequest searchRequest, List<IndexerSearchCacheEntry> indexersToSearch) {
List<IndexerCallable> callables = new ArrayList<>();

for (IndexerSearchCacheEntry toSearch : indexersToSearch) {
Callable<IndexerSearchResult> callable = getIndexerCallable(searchRequest, toSearch);
IndexerCallable callable = getIndexerCallable(searchRequest, toSearch);
callables.add(callable);
}

return callables;
}

private Callable<IndexerSearchResult> getIndexerCallable(SearchRequest searchRequest, IndexerSearchCacheEntry indexerSearchCacheEntry) {
private IndexerCallable getIndexerCallable(SearchRequest searchRequest, IndexerSearchCacheEntry indexerSearchCacheEntry) {
int offset;
if (indexerSearchCacheEntry.getIndexerSearchResults().isEmpty()) {
offset = 0;
Expand All @@ -412,7 +434,7 @@ private Callable<IndexerSearchResult> getIndexerCallable(SearchRequest searchReq
offset = indexerToSearch.getOffset() + indexerToSearch.getPageSize();
}
int limit = LOAD_LIMIT_API;
return () -> indexerSearchCacheEntry.getIndexer().search(searchRequest, offset, limit);
return new IndexerCallable(() -> indexerSearchCacheEntry.getIndexer().search(searchRequest, offset, limit), indexerSearchCacheEntry.getIndexer().getName());
}

@SuppressWarnings("unused")
Expand Down Expand Up @@ -446,4 +468,11 @@ public SearchEvent(SearchRequest searchRequest) {

}

private record SearchFutureEntry(Long searchRequestId, String indexerName) {
}

private record IndexerCallable(Callable<IndexerSearchResult> callable, String indexerName) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public class SearchRequest {

private InternalData internalData = new InternalData();

protected boolean shortcut = false;


public SearchRequest(SearchSource source, SearchType searchType, Integer offset, Integer limit) {
this.source = source;
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/resources/changelog.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#@formatter:off
- version: "v5.3.7"
changes:
- type: "feature"
text: "When you're making a search in the UI you can now choose to show the already received results. Until now you had to either wait, possibly until an indexer ran into a timeout or cancel without seeing any results at all. See #914"
- type: "feature"
text: "Add option to disable open port check. See #912"
- type: "fix"
Expand Down
17 changes: 14 additions & 3 deletions core/src/main/resources/static/js/nzbhydra.js
Original file line number Diff line number Diff line change
Expand Up @@ -9526,6 +9526,7 @@ function SearchService($http) {
search: search,
getLastResults: getLastResults,
loadMore: loadMore,
shortcutSearch: shortcutSearch,
getModalInstance: getModalInstance,
setModalInstance: setModalInstance,
};
Expand Down Expand Up @@ -9581,6 +9582,9 @@ function SearchService($http) {
return $http.post(lastExecutedQuery.toString(), lastExecutedSearchRequestParameters).then(processData);
}

function shortcutSearch(searchRequestId) {
return $http.post("internalapi/shortcutSearch/" + searchRequestId);
}

function processData(response) {
var searchResults = response.data.searchResults;
Expand Down Expand Up @@ -11501,6 +11505,8 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
$scope.indexerSelectionFinished = false;
$scope.indexersSelected = 0;
$scope.indexersFinished = 0;
$scope.buttonText = "Cancel";
$scope.btnType = "btn-danger";

var socket = new SockJS(bootstrapped.baseUrl + 'websocket');
var stompClient = Stomp.over(socket);
Expand All @@ -11518,6 +11524,10 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
if ($scope.progressMax > data.indexersSelected) {
$scope.progressMax = ">=" + data.indexersSelected;
}
if ($scope.indexersFinished > 0) {
$scope.buttonText = "Show results";
$scope.btnType = "btn-warning";
}
if (data.messages) {
$scope.messages = data.messages;
}
Expand All @@ -11528,9 +11538,10 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
});
});

$scope.cancelSearch = function () {
onCancel();
$uibModalInstance.dismiss();
$scope.shortcutSearch = function () {
SearchService.shortcutSearch(searchRequestId);
// onCancel();
// $uibModalInstance.dismiss();
};

$scope.hasResults = function (message) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/resources/static/js/nzbhydra.js.map

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions core/ui-src/html/search-state.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div class="modal-header">
<h3 style="display: inline-block" class="modal-title">Searching... please wait</h3>
<button class="btn btn-danger" type="button" ng-click="cancelSearch()"
uib-tooltip="Will not actually cancel the search but just go back to the search page. Any remaining indexer calls will be continued in the background"
<button class="btn {{btnType}}" type="button" ng-click="shortcutSearch()"
uib-tooltip="Show results that have already been loaded"
tooltip-placement="top"
tooltip-trigger="mouseenter"
style="position: absolute; right: 18px"
>Cancel
>{{buttonText}}
</button>
</div>
<div class="modal-body" style="text-align: left;">
Expand Down
13 changes: 10 additions & 3 deletions core/ui-src/js/search-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
$scope.indexerSelectionFinished = false;
$scope.indexersSelected = 0;
$scope.indexersFinished = 0;
$scope.buttonText = "Cancel";
$scope.btnType = "btn-danger";

var socket = new SockJS(bootstrapped.baseUrl + 'websocket');
var stompClient = Stomp.over(socket);
Expand All @@ -441,6 +443,10 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
if ($scope.progressMax > data.indexersSelected) {
$scope.progressMax = ">=" + data.indexersSelected;
}
if ($scope.indexersFinished > 0) {
$scope.buttonText = "Show results";
$scope.btnType = "btn-warning";
}
if (data.messages) {
$scope.messages = data.messages;
}
Expand All @@ -451,9 +457,10 @@ function SearchUpdateModalInstanceCtrl($scope, $interval, SearchService, $uibMod
});
});

$scope.cancelSearch = function () {
onCancel();
$uibModalInstance.dismiss();
$scope.shortcutSearch = function () {
SearchService.shortcutSearch(searchRequestId);
// onCancel();
// $uibModalInstance.dismiss();
};

$scope.hasResults = function (message) {
Expand Down
4 changes: 4 additions & 0 deletions core/ui-src/js/search-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function SearchService($http) {
search: search,
getLastResults: getLastResults,
loadMore: loadMore,
shortcutSearch: shortcutSearch,
getModalInstance: getModalInstance,
setModalInstance: setModalInstance,
};
Expand Down Expand Up @@ -70,6 +71,9 @@ function SearchService($http) {
return $http.post(lastExecutedQuery.toString(), lastExecutedSearchRequestParameters).then(processData);
}

function shortcutSearch(searchRequestId) {
return $http.post("internalapi/shortcutSearch/" + searchRequestId);
}

function processData(response) {
var searchResults = response.data.searchResults;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,13 @@ public ResponseEntity<? extends Object> api(NewznabParameters params, HttpServle
if (params.getQ() != null && params.getQ().startsWith("sleep20")) {
Thread.sleep(20000);
}
if (params.getQ() != null && params.getQ().startsWith("sleepforever")) {
if (params.getQ() != null && params.getQ().equals("sleeplong1") && params.getApikey().equals("1")) {
logger.info("Sleeping long for indexer 1");
Thread.sleep(10000 * 10000);
}
if (params.getQ() != null && params.getQ().equals("sleeplong")) {
Thread.sleep(10000 * 60);
}


if (params.getQ() != null && params.getQ().contains("movies")) {
Expand Down

0 comments on commit 5cec4be

Please sign in to comment.