From 88f65da57b17426b4628b878dbba4947ff678f61 Mon Sep 17 00:00:00 2001 From: jarvis2f <137974272+jarvis2f@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:52:22 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Added=20average=20download?= =?UTF-8?q?=20speed=20settings=20and=20calculation=20functionality.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/telegram/files/AvgSpeed.java | 6 +- .../java/telegram/files/HttpVerticle.java | 14 +- .../java/telegram/files/TelegramVerticle.java | 51 ++++- web/src/components/file-phase-statistics.tsx | 198 ++++++++++-------- web/src/components/settings-form.tsx | 30 +++ web/src/lib/types.ts | 1 + 6 files changed, 203 insertions(+), 97 deletions(-) diff --git a/api/src/main/java/telegram/files/AvgSpeed.java b/api/src/main/java/telegram/files/AvgSpeed.java index 8dfa985..2b3dba2 100644 --- a/api/src/main/java/telegram/files/AvgSpeed.java +++ b/api/src/main/java/telegram/files/AvgSpeed.java @@ -37,6 +37,7 @@ public class AvgSpeed { */ public void update(long downloadedSize, long timestamp) { if (downloadedSize <= 0) { + removeOldPoints(timestamp); return; } // Calculate speed since last point @@ -50,7 +51,10 @@ public class AvgSpeed { // Add new speed point speedPoints.put(timestamp, new SpeedPoint(downloadedSize, speed, timestamp)); - // Remove old points outside the interval + removeOldPoints(timestamp); + } + + private void removeOldPoints(long timestamp) { long cutoffTime = timestamp - interval * 1000L; // Convert interval to milliseconds speedPoints.headMap(cutoffTime).clear(); } diff --git a/api/src/main/java/telegram/files/HttpVerticle.java b/api/src/main/java/telegram/files/HttpVerticle.java index 9f2ad68..8fd8865 100644 --- a/api/src/main/java/telegram/files/HttpVerticle.java +++ b/api/src/main/java/telegram/files/HttpVerticle.java @@ -24,6 +24,7 @@ import io.vertx.ext.web.handler.CorsHandler; import io.vertx.ext.web.handler.SessionHandler; import io.vertx.ext.web.sstore.LocalSessionStore; import io.vertx.ext.web.sstore.SessionStore; +import telegram.files.repository.SettingKey; import telegram.files.repository.SettingRecord; import telegram.files.repository.TelegramRecord; @@ -261,18 +262,25 @@ public class HttpVerticle extends AbstractVerticle { } private void handleSettings(RoutingContext ctx) { - String keys = ctx.request().getParam("keys"); - if (StrUtil.isBlank(keys)) { + String keysStr = ctx.request().getParam("keys"); + if (StrUtil.isBlank(keysStr)) { ctx.fail(400); return; } + List keys = Arrays.asList(keysStr.split(",")); DataVerticle.settingRepository - .getByKeys(Arrays.asList(keys.split(","))) + .getByKeys(keys) .onSuccess(settings -> { JsonObject object = new JsonObject(); for (SettingRecord record : settings) { object.put(record.key(), record.value()); } + for (String key : keys) { + if (object.containsKey(key)) { + continue; + } + object.put(key, SettingKey.valueOf(key).defaultValue); + } ctx.json(object); }) .onFailure(ctx::fail); diff --git a/api/src/main/java/telegram/files/TelegramVerticle.java b/api/src/main/java/telegram/files/TelegramVerticle.java index 657f841..b37097a 100644 --- a/api/src/main/java/telegram/files/TelegramVerticle.java +++ b/api/src/main/java/telegram/files/TelegramVerticle.java @@ -57,7 +57,9 @@ public class TelegramVerticle extends AbstractVerticle { public TelegramRecord telegramRecord; - private final AvgSpeed avgSpeed = new AvgSpeed(); + private AvgSpeed avgSpeed = new AvgSpeed(); + + private long avgSpeedPersistenceTimerId; static { Client.setLogMessageHandler(0, new LogMessageHandler()); @@ -103,8 +105,9 @@ public class TelegramVerticle extends AbstractVerticle { telegramUpdateHandler.setOnFileDownloadsUpdated(this::onFileDownloadsUpdated); telegramUpdateHandler.setOnMessageReceived(this::onMessageReceived); client = Client.create(telegramUpdateHandler, this::handleException, this::handleException); - vertx.setPeriodic(60 * 1000, id -> handleSaveAvgSpeed()); - this.enableProxy(this.proxyName) + + Future.all(initEventConsumer(), initAvgSpeed()) + .compose(r -> this.enableProxy(this.proxyName)) .onSuccess(r -> startPromise.complete()) .onFailure(startPromise::fail); } @@ -225,6 +228,7 @@ public class TelegramVerticle extends AbstractVerticle { return this.execute(new TdApi.GetMessages(chatId, messageIds)) .map(m -> { Map messageMap = Arrays.stream(m.messages) + .filter(Objects::nonNull) .collect(Collectors.toMap(message -> message.id, Function.identity())); List fileRecords = r.v1.stream() .map(fileRecord -> { @@ -666,6 +670,37 @@ public class TelegramVerticle extends AbstractVerticle { StatisticRecord.Type.speed, System.currentTimeMillis(), data.encode())); + + // Avoid speed not being updated for a long time + avgSpeed.update(0, System.currentTimeMillis()); + } + + private Future initAvgSpeed() { + return DataVerticle.settingRepository.getByKey(SettingKey.avgSpeedInterval) + .compose(interval -> { + if (Objects.equals(interval, avgSpeed.getSpeedStats().interval())) { + if (avgSpeedPersistenceTimerId == 0) { + avgSpeedPersistenceTimerId = vertx.setPeriodic(interval * 1000, id -> handleSaveAvgSpeed()); + } + return Future.succeededFuture(); + } + + avgSpeed = new AvgSpeed(interval); + if (avgSpeedPersistenceTimerId != 0) { + vertx.cancelTimer(avgSpeedPersistenceTimerId); + } + avgSpeedPersistenceTimerId = vertx.setPeriodic(interval * 1000, id -> handleSaveAvgSpeed()); + return Future.succeededFuture(); + }); + } + + private Future initEventConsumer() { + vertx.eventBus().consumer(EventEnum.SETTING_UPDATE.address(SettingKey.avgSpeedInterval.name()), message -> { + log.debug("Avg Speed Interval update: %s".formatted(message.body())); + this.initAvgSpeed(); + }); + + return Future.succeededFuture(); } private void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) { @@ -883,16 +918,18 @@ public class TelegramVerticle extends AbstractVerticle { .put("avgSpeed", 0) .put("medianSpeed", 0) .put("maxSpeed", 0) - .put("minSpeed", Long.MAX_VALUE), + .put("minSpeed", 0), (a, b) -> new JsonObject() .put("avgSpeed", a.getLong("avgSpeed") + b.getLong("avgSpeed")) .put("medianSpeed", a.getLong("medianSpeed") + b.getLong("medianSpeed")) - .put("maxSpeed", Math.max(a.getLong("maxSpeed"), b.getLong("maxSpeed"))) - .put("minSpeed", Math.min(a.getLong("minSpeed"), b.getLong("minSpeed"))) + .put("maxSpeed", a.getLong("maxSpeed") + b.getLong("maxSpeed")) + .put("minSpeed", a.getLong("minSpeed") + b.getLong("minSpeed")) ); int size = entry.getValue().size(); speedStat.put("avgSpeed", speedStat.getLong("avgSpeed") / size) - .put("medianSpeed", speedStat.getLong("medianSpeed") / size); + .put("medianSpeed", speedStat.getLong("medianSpeed") / size) + .put("maxSpeed", speedStat.getLong("maxSpeed") / size) + .put("minSpeed", speedStat.getLong("minSpeed") / size); return new JsonObject() .put("time", entry.getKey()) .put("data", speedStat); diff --git a/web/src/components/file-phase-statistics.tsx b/web/src/components/file-phase-statistics.tsx index 546bb65..74ca14d 100644 --- a/web/src/components/file-phase-statistics.tsx +++ b/web/src/components/file-phase-statistics.tsx @@ -21,7 +21,7 @@ import { YAxis, } from "recharts"; import { request } from "@/lib/api"; -import prettyBytes from "pretty-bytes"; // Type definitions +import prettyBytes from "pretty-bytes"; // Type definitions type TimeRange = "1" | "2" | "3" | "4"; @@ -144,63 +144,75 @@ const TelegramStats: React.FC = ({ telegramId }) => {
- - - - - - prettyBytes(value, { bits: true }) - } - tick={axisStyle} - interval="preserveStartEnd" - /> - - prettyBytes(value, { bits: true }) - } - contentStyle={{ - backgroundColor: "rgba(255, 255, 255, 0.9)", - border: "none", - borderRadius: "6px", - boxShadow: "0 2px 4px rgba(0,0,0,0.1)", - }} - /> - - - - - - - + {!speedChartData || speedChartData.length === 0 ? ( +
+ No data available +
+ ) : ( + + + + + + prettyBytes(value, { bits: true }) + } + tick={axisStyle} + tickLine={false} + axisLine={false} + interval="preserveStartEnd" + /> + + prettyBytes(value, { bits: true }) + } + contentStyle={{ + backgroundColor: "rgba(255, 255, 255, 0.9)", + border: "none", + borderRadius: "6px", + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + fontSize: "12px", + }} + /> + + + + + + + + )}
@@ -211,34 +223,48 @@ const TelegramStats: React.FC = ({ telegramId }) => {
- - - - - - - - - - + {!completionChartData || completionChartData.length === 0 ? ( +
+ No data available +
+ ) : ( + + + + + + + + + + + )}
diff --git a/web/src/components/settings-form.tsx b/web/src/components/settings-form.tsx index a4c459e..ce87e06 100644 --- a/web/src/components/settings-form.tsx +++ b/web/src/components/settings-form.tsx @@ -28,6 +28,14 @@ export default function SettingsForm() { { value: "d", label: "crop 1280x1280" }, ]; + const avgSpeedIntervalOptions = [ + { value: "60", label: "1 minute" }, + { value: "300", label: "5 minutes" }, + { value: "600", label: "10 minutes" }, + { value: "900", label: "15 minutes" }, + { value: "1800", label: "30 minutes" }, + ] + const handleSave = async (e: FormEvent) => { e.preventDefault(); await updateSettings(); @@ -149,6 +157,28 @@ export default function SettingsForm() { }} /> +
+ + +

+ The interval to calculate the average download speed.
+ Longer intervals may consume more memory. +

+
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 70b6567..819eba4 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -90,6 +90,7 @@ export const SettingKeys = [ "showSensitiveContent", "autoDownloadLimit", "proxys", + "avgSpeedInterval", ] as const; export type SettingKey = (typeof SettingKeys)[number];