diff --git a/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java b/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java index 8f92af71..d06d5530 100644 --- a/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java +++ b/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java @@ -26,6 +26,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.SQLException; +import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; @@ -69,6 +71,8 @@ public class SearchSiteInfoService { this.searchSiteSubscriptions = searchSiteSubscriptions; } + private volatile SiteOverviewModel model = new SiteOverviewModel(List.of(), Instant.EPOCH); + @GET @Path("/site") public ModelAndView handleOverview(@QueryParam String domain) { @@ -77,6 +81,27 @@ public class SearchSiteInfoService { return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domain)); } + if (model.age().compareTo(Duration.ofMinutes(15)) > 0) { + updateModel(); + } + + return new MapModelAndView("siteinfo/start.jte", + Map.of("navbar", NavbarModel.SITEINFO, + "model", model)); + } + + /** Update the model if it is older than 15 minutes. + * This query is expensive and should not be run too often, + * and the data doesn't change that often either. + *

+ * This method is synchronized to avoid multiple threads updating the model at the same time. + */ + private synchronized void updateModel() { + var currentModel = model; + if (currentModel.age().compareTo(Duration.ofMinutes(15)) < 0) { + return; + } + List domains = new ArrayList<>(); try (var conn = dataSource.getConnection(); @@ -91,13 +116,20 @@ public class SearchSiteInfoService { throw new RuntimeException(); } - return new MapModelAndView("siteinfo/start.jte", - Map.of("navbar", NavbarModel.SITEINFO, - "model", new SiteOverviewModel(domains))); + model = new SiteOverviewModel(domains); } - public record SiteOverviewModel(List domains) { + public record SiteOverviewModel(List domains, Instant captureTime) { + + public SiteOverviewModel(List domains) { + this(domains, Instant.now()); + } + public record DiscoveredDomain(String name, String timestamp) {} + + public Duration age() { + return Duration.between(captureTime, Instant.now()); + } } @GET diff --git a/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte b/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte index 91e84a12..c3b86f4f 100644 --- a/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte +++ b/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte @@ -81,35 +81,6 @@ @endif - - @if (!siteInfo.siblingDomains().isEmpty()) -
- - Related Subdomains -
- - - - - - - - - @for (DbDomainQueries.DomainWithNode sibling : siteInfo.siblingDomains()) - - - - @endfor - -
Domain Name
- ${sibling.domain().toString()} - - @if (!sibling.isIndexed()) - - @endif -
- @endif - @if (siteInfo.domainInformation().isUnknownDomain())
@@ -178,6 +149,36 @@ @endif + + @if (!siteInfo.siblingDomains().isEmpty()) +
+ + Related Subdomains +
+ + + + + + + + + @for (DbDomainQueries.DomainWithNode sibling : siteInfo.siblingDomains()) + + + + @endfor + +
Domain Name
+ ${sibling.domain().toString()} + + @if (!sibling.isIndexed()) + + @endif +
+ @endif + + @if (siteInfo.isKnown())