From 12377d475ce44b52ed3fcb1071efe9ec7268bea6 Mon Sep 17 00:00:00 2001 From: paulzzh <28055560+paulzzh@users.noreply.github.com> Date: Thu, 21 Nov 2024 06:09:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20qB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/qbittorrent/AbstractQbittorrent.java | 78 ++++++++++--------- .../qbittorrent/impl/QBittorrentTorrent.java | 8 +- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java index 1b687a393b..2e289e6eec 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/AbstractQbittorrent.java @@ -49,7 +49,7 @@ public abstract class AbstractQbittorrent extends AbstractDownloader { protected final String apiEndpoint; protected final HttpClient httpClient; protected final QBittorrentConfig config; - protected final Cache isPrivateCache; + protected final Cache torrentPropertiesCache; public AbstractQbittorrent(String name, QBittorrentConfig config, AlertManager alertManager) { super(name, alertManager); @@ -77,7 +77,7 @@ public PasswordAuthentication requestPasswordAuthenticationInstance(String host, this.httpClient = builder.build(); YamlConfiguration profileConfig = Main.getProfileConfig(); - this.isPrivateCache = CacheBuilder.newBuilder() + this.torrentPropertiesCache = CacheBuilder.newBuilder() .maximumSize(2000) .expireAfterAccess( profileConfig.getLong("check-interval", 5000) + (1000 * 60), @@ -156,56 +156,60 @@ public List getTorrents() { List qbTorrent = JsonUtil.getGson().fromJson(request.body(), new TypeToken>() { }.getType()); - if (config.isIgnorePrivate()) { - fillTorrentPrivateField(qbTorrent); - } + fillTorrentProperties(qbTorrent); + return qbTorrent.stream().map(t -> (Torrent) t) .filter(t -> !config.isIgnorePrivate() || !t.isPrivate()) .collect(Collectors.toList()); } - protected void fillTorrentPrivateField(List qbTorrent) { - Semaphore privateStatusLimit = new Semaphore(5); + protected void fillTorrentProperties(List qbTorrent) { + Semaphore torrentPropertiesLimit = new Semaphore(5); try (ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) { qbTorrent.stream() - .filter(torrent -> torrent.getPrivateTorrent() == null) + .filter(torrent -> (config.isIgnorePrivate() && torrent.getPrivateTorrent() == null) + || torrent.getPieceSize() <= 0 || torrent.getPiecesHave() <= 0) .forEach(detail -> service.submit(() -> { - if (detail.getPrivateTorrent() == null) { - try { - privateStatusLimit.acquire(); - detail.setPrivateTorrent(getPrivateStatus(detail)); - } catch (Exception e) { - log.debug("Failed to load private cache", e); - } finally { - privateStatusLimit.release(); + try { + torrentPropertiesLimit.acquire(); + TorrentProperties properties = getTorrentProperties(detail); + if (detail.getCompleted() != properties.completed) { + // completed value changed, invalidate cache and fetch again. + torrentPropertiesCache.invalidate(detail.getHash()); + properties = getTorrentProperties(detail); + } + if (config.isIgnorePrivate() && detail.getPrivateTorrent() == null) { + log.debug("Field is_private is not present, query from properties api, hash: {}", detail.getHash()); + detail.setPrivateTorrent(properties.isPrivate); + } + if (detail.getPieceSize() <= 0 || detail.getPiecesHave() <= 0) { + log.debug("Field piece_size,pieces_have is not present, query from properties api, hash: {}", detail.getHash()); + detail.setPieceSize(properties.pieceSize); + detail.setPiecesHave(properties.piecesHave); } + } catch (Exception e) { + log.debug("Failed to load properties cache", e); + } finally { + torrentPropertiesLimit.release(); } })); } } - protected Boolean getPrivateStatus(QBittorrentTorrent torrent) { - if (torrent.getPrivateTorrent() != null) { - return torrent.getPrivateTorrent(); - } + protected TorrentProperties getTorrentProperties(QBittorrentTorrent torrent) { try { - return isPrivateCache.get(torrent.getHash(), () -> { - try { - log.debug("Field is_private is not present and cache miss, query from properties api, hash: {}", torrent.getHash()); - HttpResponse res = httpClient.send( - MutableRequest.GET(apiEndpoint + "/torrents/properties?hash=" + torrent.getHash()), - HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) - ); - if (res.statusCode() == 200) { - var newDetail = JsonUtil.getGson().fromJson(res.body(), QBittorrentTorrent.class); - return newDetail.getPrivateTorrent(); - } else { - log.warn("Error fetching properties for torrent hash: {}, status: {}", torrent.getHash(), res.statusCode()); - } - } catch (Exception e) { - log.warn("Error fetching properties for torrent hash: {}", torrent.getHash(), e); + return torrentPropertiesCache.get(torrent.getHash(), () -> { + log.debug("torrent properties cache miss, query from properties api, hash: {}", torrent.getHash()); + HttpResponse res = httpClient.send( + MutableRequest.GET(apiEndpoint + "/torrents/properties?hash=" + torrent.getHash()), + HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) + ); + if (res.statusCode() == 200) { + var newDetail = JsonUtil.getGson().fromJson(res.body(), QBittorrentTorrent.class); + return new TorrentProperties(newDetail.getPrivateTorrent(), torrent.getCompleted(), newDetail.getPieceSize(), newDetail.getPiecesHave()); } - return null; + // loader must not return null; it may either return a non-null value or throw an exception. + throw new IllegalStateException(String.format("Error fetching properties for torrent hash: %s, status: %d", torrent.getHash(), res.statusCode())); }); } catch (Exception e) { return null; @@ -322,5 +326,5 @@ public void close() throws Exception { } - + public record TorrentProperties(boolean isPrivate, long completed, long pieceSize, long piecesHave) {} } diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentTorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentTorrent.java index 82ceb4977a..6315268d4e 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentTorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/qbittorrent/impl/QBittorrentTorrent.java @@ -54,6 +54,12 @@ public final class QBittorrentTorrent implements Torrent { // @SerializedName("f_l_piece_prio") // private Boolean fLPiecePrio; + @SerializedName("piece_size") + private long pieceSize; + + @SerializedName("pieces_have") + private long piecesHave; + @SerializedName("force_start") private boolean forceStart; @@ -192,7 +198,7 @@ public long getSize() { @Override public long getCompletedSize() { - return completed; + return (pieceSize > 0 && piecesHave > 0) ? pieceSize * piecesHave : -1; } @Override