package nu.marginalia.screenshot; import com.google.common.base.Strings; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; import nu.marginalia.db.DbDomainQueries; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; import spark.Response; import java.sql.SQLException; import static java.lang.Integer.parseInt; public class ScreenshotService { private final DbDomainQueries domainQueries; private final HikariDataSource dataSource; private final Logger logger = LoggerFactory.getLogger(getClass()); @Inject public ScreenshotService(DbDomainQueries dbDomainQueries, HikariDataSource dataSource) { this.domainQueries = dbDomainQueries; this.dataSource = dataSource; } public boolean hasScreenshot(int domainId) { try (var conn = dataSource.getConnection(); var ps = conn.prepareStatement(""" SELECT TRUE FROM DATA_DOMAIN_SCREENSHOT INNER JOIN EC_DOMAIN ON EC_DOMAIN.DOMAIN_NAME=DATA_DOMAIN_SCREENSHOT.DOMAIN_NAME WHERE EC_DOMAIN.ID=? """)) { ps.setInt(1, domainId); var rs = ps.executeQuery(); if (rs.next()) { return rs.getBoolean(1); } } catch (SQLException ex) { logger.warn("SQL error", ex); } return false; } @SneakyThrows public Object serveScreenshotRequest(Request request, Response response) { if (Strings.isNullOrEmpty(request.params("id"))) { response.redirect("https://search.marginalia.nu/"); return null; } int id = parseInt(request.params("id")); try (var conn = dataSource.getConnection(); var ps = conn.prepareStatement(""" SELECT CONTENT_TYPE, DATA FROM DATA_DOMAIN_SCREENSHOT INNER JOIN EC_DOMAIN ON EC_DOMAIN.DOMAIN_NAME=DATA_DOMAIN_SCREENSHOT.DOMAIN_NAME WHERE EC_DOMAIN.ID=? """)) { ps.setInt(1, id); var rsp = ps.executeQuery(); if (rsp.next()) { response.type(rsp.getString(1)); response.status(200); response.header("Cache-control", "public,max-age=3600"); IOUtils.copy(rsp.getBlob(2).getBinaryStream(), response.raw().getOutputStream()); return ""; } } catch (SQLException ex) { logger.warn("SQL error", ex); } return serveSvgPlaceholder(response, id); } private Object serveSvgPlaceholder(Response response, int id) { var name = domainQueries.getDomain(id).map(Object::toString) .orElse("[Screenshot Not Yet Captured]"); response.type("image/svg+xml"); return """ Placeholder %s """.formatted(name); } }