diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java index 0373fa5619..7aa018a78a 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/biglybt/BiglyBT.java @@ -173,6 +173,7 @@ public List getTorrents() { detail.getName(), detail.getTorrent().getInfoHash(), detail.getTorrent().getSize(), + detail.getTorrent().getSize() - detail.getStats().getRemainingBytes(), // 种子总大小 减去 (包含未选择文件的)尚未下载大小 等于 已下载内容大小 detail.getStats().getCompletedInThousandNotation() / 1000d, detail.getStats().getRtUploadSpeed(), detail.getStats().getRtDownloadSpeed(), diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java index 756cf4740c..0c49aa0cef 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/BitComet.java @@ -293,6 +293,7 @@ public List getTorrents() { torrent.getTask().getTaskName(), torrent.getTaskDetail().getInfohash() != null ? torrent.getTaskDetail().getInfohash() : torrent.getTaskDetail().getInfohashV2(), torrent.getTaskDetail().getTotalSize(), + torrent.getTask().getSelectedDownloadedSize(), torrent.getTaskStatus().getDownloadPermillage() / 1000.0d, torrent.getTask().getUploadRate(), torrent.getTask().getDownloadRate(), diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java index 8b3c328631..1ddca99ad6 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/bitcomet/resp/BCTaskTorrentResponse.java @@ -145,8 +145,8 @@ public static class TaskDTO { private long totalSize; // @SerializedName("selected_size") // private long selectedSize; -// @SerializedName("selected_downloaded_size") -// private long selectedDownloadedSize; + @SerializedName("selected_downloaded_size") + private long selectedDownloadedSize; @SerializedName("download_rate") private long downloadRate; @SerializedName("upload_rate") diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java index aff8695804..6224500456 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/Deluge.java @@ -132,6 +132,7 @@ public List getTorrents() { activeTorrent.getInfoHash(), activeTorrent.getProgress() / 100.0d, activeTorrent.getSize(), + activeTorrent.getCompletedSize(), activeTorrent.getUploadPayloadRate(), activeTorrent.getDownloadPayloadRate(), peers, diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugeTorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugeTorrent.java index 00d15b05ab..7bc275878c 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugeTorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/deluge/DelugeTorrent.java @@ -15,6 +15,7 @@ public final class DelugeTorrent implements Torrent { private String hash; private double progress; private long size; + private long completedSize; private long rtUploadSpeed; private long rtDownloadSpeed; private List peers; 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..7364eea240 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,68 @@ 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 (properties == null) { + log.warn("Failed to retrieve properties for torrent: {}", detail.getHash()); + return; + } + if (detail.getCompleted() != properties.completed) { + // completed value changed, invalidate cache and fetch again. + torrentPropertiesCache.invalidate(detail.getHash()); + properties = getTorrentProperties(detail); + if (properties == null) { + log.warn("Failed to retrieve properties after cache invalidation for torrent: {}", detail.getHash()); + return; + } + } + if (config.isIgnorePrivate() && detail.getPrivateTorrent() == null) { + log.debug("Field is_private is not present, querying from properties API, hash: {}", detail.getHash()); + detail.setPrivateTorrent(properties.isPrivate); } + if (detail.getPieceSize() <= 0 || detail.getPiecesHave() <= 0) { + log.debug("Field piece_size or pieces_have is not present, querying 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 +334,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 aa889d7699..aeb8c9409f 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 @@ -25,8 +25,8 @@ public final class QBittorrentTorrent implements Torrent { @SerializedName("category") private String category; -// @SerializedName("completed") -// private Long completed; + @SerializedName("completed") + private long completed; // // @SerializedName("completion_on") // private Long completionOn; @@ -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; @@ -190,6 +196,11 @@ public long getSize() { return totalSize; } + @Override + public long getCompletedSize() { + return (pieceSize > 0 && piecesHave > 0) ? pieceSize * piecesHave : -1; + } + @Override public long getRtUploadSpeed() { return upspeed; diff --git a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRTorrent.java b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRTorrent.java index 5e19a3815e..20adf74606 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRTorrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/downloader/impl/transmission/TRTorrent.java @@ -40,6 +40,11 @@ public long getSize() { return backend.getTotalSize(); } + @Override + public long getCompletedSize() { + return (long) (backend.getSizeWhenDone() * backend.getPercentDone()); + } + @Override public long getRtUploadSpeed() { return backend.getRateUpload(); diff --git a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java index 976b7beee7..bdc841238f 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java +++ b/src/main/java/com/ghostchu/peerbanhelper/module/impl/rule/ProgressCheatBlocker.java @@ -229,6 +229,7 @@ private void reloadConfig() { final long actualUploaded = Math.max(peer.getUploaded(), Math.max(clientTask.getLastReportUploaded(), prefixTrackingUploadedIncreaseTotal)); try { final long torrentSize = torrent.getSize(); + final long completedSize = torrent.getCompletedSize(); // 过滤 if (torrentSize <= 0) { return pass(); @@ -253,17 +254,32 @@ private void reloadConfig() { final double actualProgress = (double) actualUploaded / torrentSize; // 实际进度 final double clientProgress = peer.getProgress(); // 客户端汇报进度 // actualUploaded = -1 代表客户端不支持统计此 Peer 总上传量 - if (actualUploaded != -1 && blockExcessiveClients && (actualUploaded > torrentSize)) { - // 下载过量,检查 - long maxAllowedExcessiveThreshold = (long) (torrentSize * excessiveThreshold); - if (actualUploaded > maxAllowedExcessiveThreshold) { - clientTask.setBanDelayWindowEndAt(0L); - progressRecorder.invalidate(client); // 封禁时,移除缓存 - return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.PCB_RULE_REACHED_MAX_ALLOWED_EXCESSIVE_THRESHOLD), - new TranslationComponent(Lang.MODULE_PCB_EXCESSIVE_DOWNLOAD, - torrentSize, - actualUploaded, - maxAllowedExcessiveThreshold)); + if (actualUploaded != -1 && blockExcessiveClients) { + if (actualUploaded > torrentSize) { + // 下载量超过种子大小,检查 + long maxAllowedExcessiveThreshold = (long) (torrentSize * excessiveThreshold); + if (actualUploaded > maxAllowedExcessiveThreshold) { + clientTask.setBanDelayWindowEndAt(0L); + progressRecorder.invalidate(client); // 封禁时,移除缓存 + return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.PCB_RULE_REACHED_MAX_ALLOWED_EXCESSIVE_THRESHOLD), + new TranslationComponent(Lang.MODULE_PCB_EXCESSIVE_DOWNLOAD, + torrentSize, + actualUploaded, + maxAllowedExcessiveThreshold)); + } + } else if (System.getProperty("pbh.pcb.disable-completed-excessive") == null && completedSize > 0 && actualUploaded > completedSize) { + // 下载量超过任务大小,检查 + long maxAllowedExcessiveThreshold = (long) (completedSize * excessiveThreshold); + if (actualUploaded > maxAllowedExcessiveThreshold) { + clientTask.setBanDelayWindowEndAt(0L); + progressRecorder.invalidate(client); // 封禁时,移除缓存 + return new CheckResult(getClass(), PeerAction.BAN, banDuration, new TranslationComponent(Lang.PCB_RULE_REACHED_MAX_ALLOWED_EXCESSIVE_THRESHOLD), + new TranslationComponent(Lang.MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE, + torrentSize, + completedSize, + actualUploaded, + maxAllowedExcessiveThreshold)); + } } } // 如果客户端报告自己进度更多,则跳过检查 diff --git a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java index 64248f103d..6d88cc35a2 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java +++ b/src/main/java/com/ghostchu/peerbanhelper/text/Lang.java @@ -28,6 +28,7 @@ public enum Lang { MODULE_IBL_MATCH_PORT, MODULE_PID_MATCH_PEER_ID, MODULE_PCB_EXCESSIVE_DOWNLOAD, + MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE, MODULE_PCB_PEER_MORE_THAN_LOCAL_SKIP, MODULE_PCB_PEER_BAN_INCORRECT_PROGRESS, MODULE_PCB_PEER_BAN_REWIND, diff --git a/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java b/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java index 8a9af82be5..42ebabfde2 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java +++ b/src/main/java/com/ghostchu/peerbanhelper/torrent/Torrent.java @@ -32,12 +32,19 @@ public interface Torrent { double getProgress(); /** - * 获取目前该 Torrent 的共计大小 + * 获取该 Torrent 的总大小 * - * @return 共计大小 + * @return 总大小 */ long getSize(); + /** + * 获取该 Torrent 已保存的数据量 (也就是最大可以提供的上传量) + * + * @return 已保存的数据量 + */ + long getCompletedSize(); + /** * 实时下载速度 * diff --git a/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java b/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java index b458b96642..25ac615613 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java +++ b/src/main/java/com/ghostchu/peerbanhelper/torrent/TorrentImpl.java @@ -12,12 +12,14 @@ public class TorrentImpl implements Torrent { private String id; private String name; private long size; + private long completedSize; - public TorrentImpl(String id, String name, String hash, long size, double progress, long rtUploadSpeed, long rtDownloadSpeed, boolean privateTorrent) { + public TorrentImpl(String id, String name, String hash, long size, long completedSize, double progress, long rtUploadSpeed, long rtDownloadSpeed, boolean privateTorrent) { this.id = id; this.name = name; this.hash = hash; this.size = size; + this.completedSize = completedSize; this.progress = progress; this.rtUploadSpeed = rtUploadSpeed; this.rtDownloadSpeed = rtDownloadSpeed; @@ -49,6 +51,11 @@ public long getSize() { return size; } + @Override + public long getCompletedSize() { + return completedSize; + } + @Override public long getRtUploadSpeed() { return rtUploadSpeed; diff --git a/src/main/java/com/ghostchu/peerbanhelper/wrapper/TorrentWrapper.java b/src/main/java/com/ghostchu/peerbanhelper/wrapper/TorrentWrapper.java index 58b4f0cf28..12eff89cde 100644 --- a/src/main/java/com/ghostchu/peerbanhelper/wrapper/TorrentWrapper.java +++ b/src/main/java/com/ghostchu/peerbanhelper/wrapper/TorrentWrapper.java @@ -11,6 +11,7 @@ public final class TorrentWrapper { private String id; private long size; + private long completedSize; private String name; private String hash; private double progress; @@ -20,6 +21,7 @@ public final class TorrentWrapper { public TorrentWrapper(Torrent torrent) { this.id = torrent.getId(); this.size = torrent.getSize(); + this.completedSize = torrent.getCompletedSize(); this.name = torrent.getName(); this.hash = torrent.getHash(); this.progress = torrent.getProgress(); diff --git a/src/main/java/raccoonfink/deluge/responses/PBHActiveTorrentsResponse.java b/src/main/java/raccoonfink/deluge/responses/PBHActiveTorrentsResponse.java index 368d77ddab..1f699cfba7 100644 --- a/src/main/java/raccoonfink/deluge/responses/PBHActiveTorrentsResponse.java +++ b/src/main/java/raccoonfink/deluge/responses/PBHActiveTorrentsResponse.java @@ -41,6 +41,8 @@ public static class ActiveTorrentsResponseDTO { private Double progress; @SerializedName("size") private Long size; + @SerializedName("completed_size") + private Long completedSize; @SerializedName("upload_payload_rate") private Long uploadPayloadRate; @SerializedName("download_payload_rate") diff --git a/src/main/resources/lang/en_us/messages.yml b/src/main/resources/lang/en_us/messages.yml index e116ff446b..c022955665 100644 --- a/src/main/resources/lang/en_us/messages.yml +++ b/src/main/resources/lang/en_us/messages.yml @@ -26,6 +26,7 @@ MODULE_IBL_EXCEPTION_GEOIP: "An exception occurred while matching GeoIP informat MODULE_IBL_MATCH_PORT: "Match Port rule: {}" MODULE_PID_MATCH_PEER_ID: "Match PeerId rule: {}" MODULE_PCB_EXCESSIVE_DOWNLOAD: "Client excessive download: Torrent size: {}, total uploaded to this peer: {}, maximum allowed excessive download total: {}" +MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE: "Client excessive download: Torrent size: {} ({} downloaded), total uploaded to this peer: {}, maximum allowed excessive download total: {}" MODULE_PCB_PEER_MORE_THAN_LOCAL_SKIP: "Client progress: {}, calculated minimal progress: {}, client progress more than local progress, skipping detection" MODULE_PCB_PEER_BAN_INCORRECT_PROGRESS: "Client progress: {}, calculated minimal: {}, difference: {}" MODULE_PCB_PEER_BAN_REWIND: "Client progress: {}, calculated minimal progress: {}; last time recorded progress: {}, rewind progress: {}, max allowed rewind threshold: {}" diff --git a/src/main/resources/lang/messages_fallback.yml b/src/main/resources/lang/messages_fallback.yml index 0ae4975f67..9e7e9e62cb 100644 --- a/src/main/resources/lang/messages_fallback.yml +++ b/src/main/resources/lang/messages_fallback.yml @@ -26,6 +26,7 @@ MODULE_IBL_MATCH_PORT: "匹配 Port 规则: {}" MODULE_PID_MATCH_PEER_ID: "匹配 PeerId 规则: {}" MODULE_IBL_MATCH_CITY: "匹配城市名称规则: {}" MODULE_PCB_EXCESSIVE_DOWNLOAD: "客户端下载过量:种子大小:{},上传给此对等体的总量:{},最大允许的过量下载总量:{}" +MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE: "客户端下载过量:种子大小:{} ({} 已下载),上传给此对等体的总量:{},最大允许的过量下载总量:{}" MODULE_PCB_PEER_MORE_THAN_LOCAL_SKIP: "客户端进度:{},实际进度:{},客户端的进度多于本地进度,跳过检测" MODULE_PCB_PEER_BAN_INCORRECT_PROGRESS: "客户端进度:{},实际进度:{},差值:{}" MODULE_PCB_PEER_BAN_REWIND: "客户端进度:{},实际进度:{},上次记录进度:{},本次回退进度:{},差值:{}" diff --git a/src/main/resources/lang/zh_cn/messages.yml b/src/main/resources/lang/zh_cn/messages.yml index ad580d6e73..2a9f92e558 100644 --- a/src/main/resources/lang/zh_cn/messages.yml +++ b/src/main/resources/lang/zh_cn/messages.yml @@ -26,6 +26,7 @@ MODULE_IBL_MATCH_PORT: "匹配 Port 规则: {}" MODULE_PID_MATCH_PEER_ID: "匹配 PeerId 规则: {}" MODULE_IBL_MATCH_CITY: "匹配城市名称规则: {}" MODULE_PCB_EXCESSIVE_DOWNLOAD: "客户端下载过量:种子大小:{},上传给此对等体的总量:{},最大允许的过量下载总量:{}" +MODULE_PCB_EXCESSIVE_DOWNLOAD_INCOMPLETE: "客户端下载过量:种子大小:{} ({} 已下载),上传给此对等体的总量:{},最大允许的过量下载总量:{}" MODULE_PCB_PEER_MORE_THAN_LOCAL_SKIP: "客户端进度:{},实际进度:{},客户端的进度多于本地进度,跳过检测" MODULE_PCB_PEER_BAN_INCORRECT_PROGRESS: "客户端进度:{},实际进度:{},差值:{}" MODULE_PCB_PEER_BAN_REWIND: "客户端进度:{},实际进度:{},上次记录进度:{},本次回退进度:{},差值:{}"