From 959b41e3b90222b983869364a44f347ae23198c7 Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sat, 30 Dec 2023 17:13:57 +0100 Subject: [PATCH] Fix thumbnail caching --- CHANGELOG.md | 1 + .../java/ctbrec/ui/CamrecApplication.java | 8 +- .../java/ctbrec/ui/settings/SettingsTab.java | 2 +- .../sites/jasmin/LiveJasminUpdateService.java | 23 ++-- .../main/java/ctbrec/ui/tabs/ThumbCell.java | 6 +- .../src/main/java/ctbrec/io/HttpClient.java | 103 +++++++++++------- .../ctbrec/io/HttpClientCacheProvider.java | 46 ++++++++ 7 files changed, 131 insertions(+), 58 deletions(-) create mode 100644 common/src/main/java/ctbrec/io/HttpClientCacheProvider.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 492bba88..468f06bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Added menu entry to force recording of models without changing the prio * Added blacklist and whitelist settings to automatically filter out models * Added setting to delete orphaned recording metadata (switched off by default) +* Fixed thumbnail caching 5.2.3 ======================== diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index 0f870944..d085481a 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -14,10 +14,7 @@ import ctbrec.event.EventHandlerConfiguration; import ctbrec.image.LocalPortraitStore; import ctbrec.image.PortraitStore; import ctbrec.image.RemotePortraitStore; -import ctbrec.io.BandwidthMeter; -import ctbrec.io.ByteUnitFormatter; -import ctbrec.io.HttpClient; -import ctbrec.io.HttpException; +import ctbrec.io.*; import ctbrec.io.json.ObjectMapperFactory; import ctbrec.notes.LocalModelNotesService; import ctbrec.notes.ModelNotesService; @@ -434,6 +431,9 @@ public class CamrecApplication extends Application { } try { ExternalBrowser.getInstance().close(); + HttpClientCacheProvider.getCache(config).evictAll(); + HttpClientCacheProvider.getCache(config).close(); + IoUtils.deleteDirectory(new File(config.getConfigDir(), "cache")); } catch (IOException e12) { // noop } diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index 01618358..cbf04842 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -234,7 +234,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { Setting.of("Update overview interval (seconds)", overviewUpdateIntervalInSecs, "Update the thumbnail overviews every x seconds").needsRestart(), Setting.of("Update thumbnails", updateThumbnails, "The overviews will still be updated, but the thumbnails won't be changed. This is useful for less powerful systems."), - Setting.of("Cache size", new CacheSettingsPane(this, config)).needsRestart(), + Setting.of("Thumbnails cache size", new CacheSettingsPane(this, config)).needsRestart(), Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"), Setting.of("Enable live previews (experimental)", livePreviews), Setting.of("Enable recently watched tab", recentlyWatched).needsRestart(), diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java index 5e0623cc..b3c7ca42 100644 --- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java +++ b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java @@ -170,19 +170,20 @@ public class LiveJasminUpdateService extends PaginatedScheduledService { } listPageId = content.optString("listPageId"); JSONArray performers = content.getJSONArray("performers"); - for (var i = 0; i < performers.length(); i++) { - var m = performers.getJSONObject(i); - var name = m.optString("pid"); - if (name.isEmpty()) { - continue; - } - LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name); - model.setId(m.getString("id")); - model.setPreview(m.optString("profilePictureUrl")); + for (var i = 0; i < performers.length(); i++) { + var m = performers.getJSONObject(i); + var name = m.optString("pid"); + if (name.isEmpty()) { + continue; + } + LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name); + model.setId(m.getString("id")); + model.setPreview(m.optString("profilePictureUrl")); model.setOnlineState(LiveJasminModel.mapStatus(m.optInt("status"))); model.setDisplayName(m.optString("display_name", null)); - models.add(model); - }} // if content + models.add(model); + } + } // if content } // if data } } diff --git a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java index 2a28487c..14d035eb 100644 --- a/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java +++ b/client/src/main/java/ctbrec/ui/tabs/ThumbCell.java @@ -429,7 +429,7 @@ public class ThumbCell extends StackPane { .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(REFERER, getModel().getSite().getBaseUrl()) .build(); - try (Response resp = model.getSite().getHttpClient().execute(req)) { + try (Response resp = model.getSite().getHttpClient().executeWithCache(req)) { if (resp.isSuccessful()) { double width = 480; double height = width * imgAspectRatio; @@ -500,9 +500,9 @@ public class ThumbCell extends StackPane { } else { modelRecordingState = ModelRecordingState.RECORDING; if (model.isForcePriority()) { - recordingIndicator.setImage(imgForceRecordIndicator); + recordingIndicator.setImage(imgForceRecordIndicator); } else { - recordingIndicator.setImage(imgRecordIndicator); + recordingIndicator.setImage(imgRecordIndicator); } recordingIndicatorTooltip.setText("Pause Recording"); } diff --git a/common/src/main/java/ctbrec/io/HttpClient.java b/common/src/main/java/ctbrec/io/HttpClient.java index 69dd3fb1..e90a6b79 100644 --- a/common/src/main/java/ctbrec/io/HttpClient.java +++ b/common/src/main/java/ctbrec/io/HttpClient.java @@ -8,6 +8,7 @@ import ctbrec.io.json.ObjectMapperFactory; import ctbrec.io.json.dto.CookieDto; import ctbrec.io.json.mapper.CookieMapper; import lombok.Data; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okhttp3.OkHttpClient.Builder; @@ -38,11 +39,14 @@ import static java.nio.charset.StandardCharsets.UTF_8; public abstract class HttpClient { private static final ConnectionPool GLOBAL_HTTP_CONN_POOL = new ConnectionPool(10, 2, TimeUnit.MINUTES); + @Getter + protected CookieJarImpl cookieJar; protected OkHttpClient client; protected Cache cache; - protected CookieJarImpl cookieJar; protected Config config; protected boolean loggedIn = false; + protected long cacheSize; + protected int cacheLifeTime = 600; private final String name; protected HttpClient(String name, Config config) { @@ -60,26 +64,26 @@ public abstract class HttpClient { ProxyType proxyType = config.getSettings().proxyType; switch (proxyType) { case HTTP: - System.setProperty("http.proxyHost", config.getSettings().proxyHost); - System.setProperty("http.proxyPort", config.getSettings().proxyPort); - System.setProperty("https.proxyHost", config.getSettings().proxyHost); - System.setProperty("https.proxyPort", config.getSettings().proxyPort); + System.setProperty(ProxyConstants.HTTP_PROXY_HOST, config.getSettings().proxyHost); + System.setProperty(ProxyConstants.HTTP_PROXY_PORT, config.getSettings().proxyPort); + System.setProperty(ProxyConstants.HTTPS_PROXY_HOST, config.getSettings().proxyHost); + System.setProperty(ProxyConstants.HTTPS_PROXY_PORT, config.getSettings().proxyPort); if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) { String username = config.getSettings().proxyUser; String password = config.getSettings().proxyPassword; - System.setProperty("http.proxyUser", username); - System.setProperty("http.proxyPassword", password); + System.setProperty(ProxyConstants.HTTP_PROXY_USER, username); + System.setProperty(ProxyConstants.HTTP_PROXY_PASSWORD, password); } break; case SOCKS4: - System.setProperty("socksProxyVersion", "4"); - System.setProperty("socksProxyHost", config.getSettings().proxyHost); - System.setProperty("socksProxyPort", config.getSettings().proxyPort); + System.setProperty(ProxyConstants.SOCKS_PROXY_VERSION, "4"); + System.setProperty(ProxyConstants.SOCKS_PROXY_HOST, config.getSettings().proxyHost); + System.setProperty(ProxyConstants.SOCKS_PROXY_PORT, config.getSettings().proxyPort); break; case SOCKS5: - System.setProperty("socksProxyVersion", "5"); - System.setProperty("socksProxyHost", config.getSettings().proxyHost); - System.setProperty("socksProxyPort", config.getSettings().proxyPort); + System.setProperty(ProxyConstants.SOCKS_PROXY_VERSION, "5"); + System.setProperty(ProxyConstants.SOCKS_PROXY_HOST, config.getSettings().proxyHost); + System.setProperty(ProxyConstants.SOCKS_PROXY_PORT, config.getSettings().proxyPort); if (config.getSettings().proxyUser != null && !config.getSettings().proxyUser.isEmpty()) { String username = config.getSettings().proxyUser; String password = config.getSettings().proxyPassword; @@ -88,44 +92,55 @@ public abstract class HttpClient { break; case DIRECT: default: - System.clearProperty("http.proxyHost"); - System.clearProperty("http.proxyPort"); - System.clearProperty("https.proxyHost"); - System.clearProperty("https.proxyPort"); - System.clearProperty("socksProxyVersion"); - System.clearProperty("socksProxyHost"); - System.clearProperty("socksProxyPort"); - System.clearProperty("java.net.socks.username"); - System.clearProperty("java.net.socks.password"); - System.clearProperty("http.proxyUser"); - System.clearProperty("http.proxyPassword"); + System.clearProperty(ProxyConstants.HTTP_PROXY_HOST); + System.clearProperty(ProxyConstants.HTTP_PROXY_PORT); + System.clearProperty(ProxyConstants.HTTPS_PROXY_HOST); + System.clearProperty(ProxyConstants.HTTPS_PROXY_PORT); + System.clearProperty(ProxyConstants.SOCKS_PROXY_VERSION); + System.clearProperty(ProxyConstants.SOCKS_PROXY_HOST); + System.clearProperty(ProxyConstants.SOCKS_PROXY_PORT); + System.clearProperty(ProxyConstants.JAVA_NET_SOCKS_USERNAME); + System.clearProperty(ProxyConstants.JAVA_NET_SOCKS_PASSWORD); + System.clearProperty(ProxyConstants.HTTP_PROXY_USER); + System.clearProperty(ProxyConstants.HTTP_PROXY_PASSWORD); break; } } public Response execute(Request req) throws IOException { - if (cache != null) { - log.trace("Cache hit ratio {}/{} = {}", cache.hitCount(), cache.requestCount(), NumberFormat.getPercentInstance().format(cache.hitCount() / (double) cache.requestCount())); - } Response resp = client.newCall(req).execute(); return resp; } public Response execute(Request request, int timeoutInMillis) throws IOException { - if (cache != null) { - log.trace("Cache hit ratio {}/{} = {}", cache.hitCount(), cache.requestCount(), NumberFormat.getPercentInstance().format(cache.hitCount() / (double) cache.requestCount())); - } return client.newBuilder() // .connectTimeout(timeoutInMillis, TimeUnit.MILLISECONDS) // .readTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).build() // .newCall(request).execute(); } + public Response executeWithCache(Request req) throws IOException { + log.trace("Cached request for {}", req.url()); + if (Objects.nonNull(cache)) { + log.trace("Cache hit ratio {}/{} = {}", cache.hitCount(), cache.requestCount(), NumberFormat.getPercentInstance().format(cache.hitCount() / (double) cache.requestCount())); + } + if (cacheSize > 0 && Objects.nonNull(cache)) { + Request r = req.newBuilder() + .cacheControl(new CacheControl.Builder().maxAge(cacheLifeTime, TimeUnit.SECONDS).build()) + .build(); + return execute(r); + } else { + return execute(req); + } + } + public abstract boolean login() throws IOException; public void reconfigure() { loadProxySettings(); loadCookies(); + cacheSize = (long) config.getSettings().thumbCacheSize * 1024 * 1024; + Builder builder = new OkHttpClient.Builder() .cookieJar(cookieJar) .connectionPool(GLOBAL_HTTP_CONN_POOL) @@ -133,12 +148,11 @@ public abstract class HttpClient { .readTimeout(config.getSettings().httpTimeout, TimeUnit.MILLISECONDS) .addNetworkInterceptor(new LoggingInterceptor()); - long cacheSize = (long) config.getSettings().thumbCacheSize * 1024 * 1024; if (cacheSize > 0) { - File configDir = config.getConfigDir(); - File cacheDir = new File(configDir, "cache"); - cache = new Cache(cacheDir, cacheSize); - builder.cache(cache); + cache = HttpClientCacheProvider.getCache(config); + if (cache != null) { + builder.cache(cache); + } } ProxyType proxyType = config.getSettings().proxyType; @@ -265,10 +279,6 @@ public abstract class HttpClient { } } - public CookieJarImpl getCookieJar() { - return cookieJar; - } - public void logout() { getCookieJar().clear(); loggedIn = false; @@ -332,4 +342,19 @@ public abstract class HttpClient { public void clearCookies() { logout(); } + + private static class ProxyConstants { + public static final String HTTP_PROXY_HOST = "http.proxyHost"; + public static final String HTTP_PROXY_PORT = "http.proxyPort"; + public static final String HTTPS_PROXY_HOST = "https.proxyHost"; + public static final String HTTPS_PROXY_PORT = "https.proxyPort"; + public static final String HTTP_PROXY_USER = "https.proxyUser"; + public static final String HTTP_PROXY_PASSWORD = "https.proxyPassword"; + + public static final String SOCKS_PROXY_HOST = "socksProxyHost"; + public static final String SOCKS_PROXY_PORT = "socksProxyPort"; + public static final String SOCKS_PROXY_VERSION = "socksProxyVersion"; + public static final String JAVA_NET_SOCKS_USERNAME = "java.net.socks.username"; + public static final String JAVA_NET_SOCKS_PASSWORD = "java.net.socks.password"; + } } diff --git a/common/src/main/java/ctbrec/io/HttpClientCacheProvider.java b/common/src/main/java/ctbrec/io/HttpClientCacheProvider.java new file mode 100644 index 00000000..86a5c9c7 --- /dev/null +++ b/common/src/main/java/ctbrec/io/HttpClientCacheProvider.java @@ -0,0 +1,46 @@ +package ctbrec.io; + +import ctbrec.Config; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Cache; + +import java.io.File; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public class HttpClientCacheProvider { + + private static final Lock lock = new ReentrantLock(); + private static HttpClientCacheProvider provider; + + private Cache cache; + + private HttpClientCacheProvider(Config config) { + File configDir = config.getConfigDir(); + File cacheDir = new File(configDir, "cache"); + long cacheSize = (long) config.getSettings().thumbCacheSize * 1024 * 1024; + try { + cache = new Cache(cacheDir, cacheSize); + } catch (Exception ex) { + log.error("Could not create HTTP client cache", ex); + } + } + + private Cache getCache() { + return cache; + } + + public static Cache getCache(Config config) { + lock.lock(); + try { + if (provider == null) { + provider = new HttpClientCacheProvider(config); + } + return provider.getCache(); + } finally { + lock.unlock(); + } + } + +}