⚡️ feat: Optimize websocket binding method, optimize download speed event current limit
This commit is contained in:
parent
23c5758e0f
commit
d3f81c9a14
9 changed files with 234 additions and 158 deletions
|
|
@ -110,7 +110,7 @@ public class AutoDownloadVerticle extends AbstractVerticle {
|
||||||
});
|
});
|
||||||
vertx.eventBus().consumer(EventEnum.SETTING_UPDATE.address(SettingKey.autoDownloadLimit.name()), message -> {
|
vertx.eventBus().consumer(EventEnum.SETTING_UPDATE.address(SettingKey.autoDownloadLimit.name()), message -> {
|
||||||
log.debug("Auto download limit update: %s".formatted(message.body()));
|
log.debug("Auto download limit update: %s".formatted(message.body()));
|
||||||
this.limit = Convert.toInt(message.body());
|
this.limit = Convert.toInt(message.body(), DEFAULT_LIMIT);
|
||||||
});
|
});
|
||||||
vertx.eventBus().consumer(EventEnum.MESSAGE_RECEIVED.address(), message -> {
|
vertx.eventBus().consumer(EventEnum.MESSAGE_RECEIVED.address(), message -> {
|
||||||
log.trace("Auto download message received: %s".formatted(message.body()));
|
log.trace("Auto download message received: %s".formatted(message.body()));
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,14 @@ public enum EventEnum {
|
||||||
* body = JSONObject with "telegramId", "chatId", "messageId"
|
* body = JSONObject with "telegramId", "chatId", "messageId"
|
||||||
*/
|
*/
|
||||||
MESSAGE_RECEIVED,
|
MESSAGE_RECEIVED,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* suffix = null <br>
|
||||||
|
* body = JSONObject with "telegramId", "payload"
|
||||||
|
*
|
||||||
|
* @see telegram.files.EventPayload
|
||||||
|
*/
|
||||||
|
TELEGRAM_EVENT,
|
||||||
;
|
;
|
||||||
|
|
||||||
public String address() {
|
public String address() {
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import io.vertx.core.http.CookieSameSite;
|
||||||
import io.vertx.core.http.HttpMethod;
|
import io.vertx.core.http.HttpMethod;
|
||||||
import io.vertx.core.http.HttpServerOptions;
|
import io.vertx.core.http.HttpServerOptions;
|
||||||
import io.vertx.core.http.HttpServerResponse;
|
import io.vertx.core.http.HttpServerResponse;
|
||||||
|
import io.vertx.core.json.Json;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.healthchecks.HealthCheckHandler;
|
import io.vertx.ext.healthchecks.HealthCheckHandler;
|
||||||
|
|
@ -55,6 +56,7 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
initHttpServer()
|
initHttpServer()
|
||||||
.compose(r -> initTelegramVerticles())
|
.compose(r -> initTelegramVerticles())
|
||||||
.compose(r -> initAutoDownloadVerticle())
|
.compose(r -> initAutoDownloadVerticle())
|
||||||
|
.compose(r -> initEventConsumer())
|
||||||
.onSuccess(startPromise::complete)
|
.onSuccess(startPromise::complete)
|
||||||
.onFailure(startPromise::fail);
|
.onFailure(startPromise::fail);
|
||||||
}
|
}
|
||||||
|
|
@ -216,12 +218,37 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
.mapEmpty();
|
.mapEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Future<Void> initEventConsumer() {
|
||||||
|
vertx.eventBus().consumer(EventEnum.TELEGRAM_EVENT.address(), message -> {
|
||||||
|
log.debug("Received telegram event: %s".formatted(message.body()));
|
||||||
|
JsonObject jsonObject = (JsonObject) message.body();
|
||||||
|
String telegramId = jsonObject.getString("telegramId");
|
||||||
|
EventPayload payload = jsonObject.getJsonObject("payload").mapTo(EventPayload.class);
|
||||||
|
|
||||||
|
sessionTelegramVerticles.entrySet().stream()
|
||||||
|
.filter(e -> Objects.equals(Convert.toStr(e.getValue().getId()), telegramId))
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.forEach(sessionId -> {
|
||||||
|
String wsHandlerId = clients.get(sessionId);
|
||||||
|
if (StrUtil.isNotBlank(wsHandlerId)) {
|
||||||
|
vertx.eventBus().send(wsHandlerId, Json.encode(payload));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Future.succeededFuture();
|
||||||
|
}
|
||||||
|
|
||||||
private void handleWebSocket(RoutingContext ctx) {
|
private void handleWebSocket(RoutingContext ctx) {
|
||||||
String sessionId = ctx.session().id();
|
String sessionId = ctx.session().id();
|
||||||
|
String telegramId = ctx.request().getParam("telegramId");
|
||||||
ctx.request().toWebSocket()
|
ctx.request().toWebSocket()
|
||||||
.onSuccess(ws -> {
|
.onSuccess(ws -> {
|
||||||
log.debug("Upgraded to WebSocket. SessionId: %s".formatted(sessionId));
|
log.debug("Upgraded to WebSocket. SessionId: %s".formatted(sessionId));
|
||||||
clients.put(sessionId, ws.textHandlerID());
|
clients.put(sessionId, ws.textHandlerID());
|
||||||
|
if (StrUtil.isNotBlank(telegramId) && !handleTelegramChange(sessionId, telegramId)) {
|
||||||
|
log.debug("Failed to change telegram verticle. SessionId: %s".formatted(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
long timerId = vertx.setPeriodic(30000, id -> {
|
long timerId = vertx.setPeriodic(30000, id -> {
|
||||||
if (!ws.isClosed()) {
|
if (!ws.isClosed()) {
|
||||||
|
|
@ -237,9 +264,7 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
log.debug("WebSocket closed. SessionId: %s".formatted(sessionId));
|
log.debug("WebSocket closed. SessionId: %s".formatted(sessionId));
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.textMessageHandler(text -> {
|
ws.textMessageHandler(text -> log.debug("Received WebSocket message: " + text));
|
||||||
log.debug("Received WebSocket message: " + text);
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.onFailure(err -> log.warn("Failed to upgrade to WebSocket: %s".formatted(err.getMessage())));
|
.onFailure(err -> log.warn("Failed to upgrade to WebSocket: %s".formatted(err.getMessage())));
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +328,6 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
String proxyName = jsonObject.getString("proxyName");
|
String proxyName = jsonObject.getString("proxyName");
|
||||||
|
|
||||||
TelegramVerticle newTelegramVerticle = new TelegramVerticle(DataVerticle.telegramRepository.getRootPath());
|
TelegramVerticle newTelegramVerticle = new TelegramVerticle(DataVerticle.telegramRepository.getRootPath());
|
||||||
newTelegramVerticle.bindHttpSession(sessionId);
|
|
||||||
newTelegramVerticle.setProxy(proxyName);
|
newTelegramVerticle.setProxy(proxyName);
|
||||||
sessionTelegramVerticles.put(sessionId, newTelegramVerticle);
|
sessionTelegramVerticles.put(sessionId, newTelegramVerticle);
|
||||||
telegramVerticles.add(newTelegramVerticle);
|
telegramVerticles.add(newTelegramVerticle);
|
||||||
|
|
@ -356,11 +380,10 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
String query = ctx.request().getParam("query");
|
String query = ctx.request().getParam("query");
|
||||||
String chatId = ctx.request().getParam("chatId");
|
String chatId = ctx.request().getParam("chatId");
|
||||||
getTelegramVerticle(telegramId)
|
getTelegramVerticle(telegramId)
|
||||||
.ifPresentOrElse(telegramVerticle -> {
|
.ifPresentOrElse(telegramVerticle ->
|
||||||
telegramVerticle.getChats(Convert.toLong(chatId), query)
|
telegramVerticle.getChats(Convert.toLong(chatId), query)
|
||||||
.onSuccess(ctx::json)
|
.onSuccess(ctx::json)
|
||||||
.onFailure(ctx::fail);
|
.onFailure(ctx::fail), () -> ctx.fail(404));
|
||||||
}, () -> ctx.fail(404));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTelegramFiles(RoutingContext ctx) {
|
private void handleTelegramFiles(RoutingContext ctx) {
|
||||||
|
|
@ -427,16 +450,24 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
private void handleTelegramChange(RoutingContext ctx) {
|
private void handleTelegramChange(RoutingContext ctx) {
|
||||||
String sessionId = ctx.session().id();
|
String sessionId = ctx.session().id();
|
||||||
String telegramId = ctx.request().getParam("telegramId");
|
String telegramId = ctx.request().getParam("telegramId");
|
||||||
|
if (handleTelegramChange(sessionId, telegramId)) {
|
||||||
|
ctx.end();
|
||||||
|
} else {
|
||||||
|
ctx.fail(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleTelegramChange(String sessionId, String telegramId) {
|
||||||
if (StrUtil.isBlank(telegramId)) {
|
if (StrUtil.isBlank(telegramId)) {
|
||||||
sessionTelegramVerticles.remove(sessionId);
|
sessionTelegramVerticles.remove(sessionId);
|
||||||
ctx.end();
|
return true;
|
||||||
}
|
}
|
||||||
getTelegramVerticle(telegramId)
|
Optional<TelegramVerticle> optionalTelegramVerticle = getTelegramVerticle(telegramId);
|
||||||
.ifPresentOrElse(telegramVerticle -> {
|
if (optionalTelegramVerticle.isEmpty()) {
|
||||||
telegramVerticle.bindHttpSession(sessionId);
|
return false;
|
||||||
sessionTelegramVerticles.put(sessionId, telegramVerticle);
|
}
|
||||||
ctx.end();
|
sessionTelegramVerticles.put(sessionId, optionalTelegramVerticle.get());
|
||||||
}, () -> ctx.fail(404));
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTelegramToggleProxy(RoutingContext ctx) {
|
private void handleTelegramToggleProxy(RoutingContext ctx) {
|
||||||
|
|
@ -478,13 +509,11 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
ctx.fail(400);
|
ctx.fail(400);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String sessionId = ctx.session().id();
|
|
||||||
TelegramVerticle telegramVerticle = getTelegramVerticle(ctx);
|
TelegramVerticle telegramVerticle = getTelegramVerticle(ctx);
|
||||||
if (telegramVerticle == null) {
|
if (telegramVerticle == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonObject params = ctx.body().asJsonObject();
|
JsonObject params = ctx.body().asJsonObject();
|
||||||
telegramVerticle.bindHttpSession(sessionId);
|
|
||||||
telegramVerticle.execute(method, params == null ? null : params.getMap())
|
telegramVerticle.execute(method, params == null ? null : params.getMap())
|
||||||
.onSuccess(code -> ctx.json(JsonObject.of("code", code)))
|
.onSuccess(code -> ctx.json(JsonObject.of("code", code)))
|
||||||
.onFailure(ctx::fail);
|
.onFailure(ctx::fail);
|
||||||
|
|
@ -637,10 +666,6 @@ public class HttpVerticle extends AbstractVerticle {
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getWSHandlerId(String sessionId) {
|
|
||||||
return clients.get(sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TelegramVerticle getTelegramVerticle(RoutingContext ctx) {
|
private TelegramVerticle getTelegramVerticle(RoutingContext ctx) {
|
||||||
String sessionId = ctx.session().id();
|
String sessionId = ctx.session().id();
|
||||||
TelegramVerticle telegramVerticle = sessionTelegramVerticles.get(sessionId);
|
TelegramVerticle telegramVerticle = sessionTelegramVerticles.get(sessionId);
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
private Client client;
|
private Client client;
|
||||||
|
|
||||||
private volatile String httpSessionId;
|
|
||||||
|
|
||||||
public boolean authorized = false;
|
public boolean authorized = false;
|
||||||
|
|
||||||
public TdApi.AuthorizationState lastAuthorizationState;
|
public TdApi.AuthorizationState lastAuthorizationState;
|
||||||
|
|
@ -62,6 +60,10 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
private long avgSpeedPersistenceTimerId;
|
private long avgSpeedPersistenceTimerId;
|
||||||
|
|
||||||
|
private long lastFileEventTime;
|
||||||
|
|
||||||
|
private long lastFileDownloadEventTime;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Client.setLogMessageHandler(0, new LogMessageHandler());
|
Client.setLogMessageHandler(0, new LogMessageHandler());
|
||||||
|
|
||||||
|
|
@ -137,10 +139,6 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindHttpSession(String httpSessionId) {
|
|
||||||
this.httpSessionId = httpSessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<JsonObject> getTelegramAccount() {
|
public Future<JsonObject> getTelegramAccount() {
|
||||||
return Future.future(promise -> {
|
return Future.future(promise -> {
|
||||||
if (!authorized) {
|
if (!authorized) {
|
||||||
|
|
@ -441,11 +439,6 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
return this.execute(new TdApi.ToggleDownloadIsPaused(fileId, isPaused));
|
return this.execute(new TdApi.ToggleDownloadIsPaused(fileId, isPaused));
|
||||||
})
|
})
|
||||||
.onSuccess(r ->
|
|
||||||
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_STATUS, new JsonObject()
|
|
||||||
.put("fileId", fileId)
|
|
||||||
.put("downloadStatus", isPaused ? FileRecord.DownloadStatus.paused : FileRecord.DownloadStatus.downloading)
|
|
||||||
)))
|
|
||||||
.mapEmpty();
|
.mapEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -644,14 +637,8 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendHttpEvent(EventPayload payload) {
|
private void sendHttpEvent(EventPayload payload) {
|
||||||
if (this.httpSessionId == null) return;
|
vertx.eventBus().send(EventEnum.TELEGRAM_EVENT.address(),
|
||||||
|
JsonObject.of("telegramId", this.getId(), "payload", JsonObject.mapFrom(payload)));
|
||||||
String address = HttpVerticle.getWSHandlerId(httpSessionId);
|
|
||||||
if (address == null) {
|
|
||||||
log.debug("[%s] Can not found websocket textHandlerID for session id:%s".formatted(getRootId(), httpSessionId));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
vertx.eventBus().send(address, Json.encode(payload));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleAuthorizationResult(TdApi.Object object) {
|
private void handleAuthorizationResult(TdApi.Object object) {
|
||||||
|
|
@ -797,6 +784,7 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
String localPath = null;
|
String localPath = null;
|
||||||
Long completionDate = null;
|
Long completionDate = null;
|
||||||
|
long downloadedSize = file.local == null ? 0 : file.local.downloadedSize;
|
||||||
if (file.local != null && file.local.isDownloadingCompleted) {
|
if (file.local != null && file.local.isDownloadingCompleted) {
|
||||||
localPath = file.local.path;
|
localPath = file.local.path;
|
||||||
completionDate = System.currentTimeMillis();
|
completionDate = System.currentTimeMillis();
|
||||||
|
|
@ -814,16 +802,23 @@ public class TelegramVerticle extends AbstractVerticle {
|
||||||
.put("downloadStatus", r.getString("downloadStatus"))
|
.put("downloadStatus", r.getString("downloadStatus"))
|
||||||
.put("localPath", r.getString("localPath"))
|
.put("localPath", r.getString("localPath"))
|
||||||
.put("completionDate", r.getLong("completionDate"))
|
.put("completionDate", r.getLong("completionDate"))
|
||||||
|
.put("downloadedSize", downloadedSize)
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE, updateFile));
|
if (lastFileEventTime == 0 || System.currentTimeMillis() - lastFileEventTime > 1000) {
|
||||||
|
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE, updateFile));
|
||||||
|
lastFileEventTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFileDownloadsUpdated(TdApi.UpdateFileDownloads updateFileDownloads) {
|
private void onFileDownloadsUpdated(TdApi.UpdateFileDownloads updateFileDownloads) {
|
||||||
log.trace("[%s] Receive file downloads update: %s".formatted(getRootId(), updateFileDownloads));
|
log.trace("[%s] Receive file downloads update: %s".formatted(getRootId(), updateFileDownloads));
|
||||||
avgSpeed.update(updateFileDownloads.downloadedSize, System.currentTimeMillis());
|
avgSpeed.update(updateFileDownloads.downloadedSize, System.currentTimeMillis());
|
||||||
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_DOWNLOAD, updateFileDownloads));
|
if (lastFileDownloadEventTime == 0 || System.currentTimeMillis() - lastFileDownloadEventTime > 1000) {
|
||||||
|
sendHttpEvent(EventPayload.build(EventPayload.TYPE_FILE_DOWNLOAD, updateFileDownloads));
|
||||||
|
lastFileDownloadEventTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMessageReceived(TdApi.Message message) {
|
private void onMessageReceived(TdApi.Message message) {
|
||||||
|
|
|
||||||
|
|
@ -171,8 +171,8 @@ export default function AccountCreator({
|
||||||
|
|
||||||
if (!authState && !isMethodExecuting) {
|
if (!authState && !isMethodExecuting) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center space-x-2 text-xl">
|
<div className="flex flex-col items-center justify-center space-y-2 rounded bg-gray-50 p-2">
|
||||||
<p>Waiting for the telegram account to be initialized, please wait</p>
|
<p>Waiting for the telegram account to be initialized, please wait.</p>
|
||||||
<p>If it takes too long, please refresh the page or try again later.</p>
|
<p>If it takes too long, please refresh the page or try again later.</p>
|
||||||
<Ellipsis className="h-4 w-4 animate-pulse" />
|
<Ellipsis className="h-4 w-4 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -25,110 +25,141 @@ interface FileExtraProps {
|
||||||
rowHeight: RowHeight;
|
rowHeight: RowHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileExtra({ file, rowHeight }: FileExtraProps) {
|
function FileName({ file }: { file: TelegramFile }) {
|
||||||
|
if (!file.fileName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<Mountain className="h-4 w-4" />
|
||||||
|
<span className="rounded px-1 text-sm hover:bg-gray-100">
|
||||||
|
{file.fileName}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</SpoiledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileCaption({ file, rowHeight }: FileExtraProps) {
|
||||||
|
if (!file.caption) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Captions className="h-4 w-4 flex-shrink-0" />
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
rowHeight !== "l" && "line-clamp-1",
|
||||||
|
"overflow-hidden truncate text-ellipsis text-wrap text-start text-sm",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{file.caption}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p
|
||||||
|
className="max-w-80 text-wrap rounded p-2"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: file.caption.replaceAll("\n", "<br />"),
|
||||||
|
}}
|
||||||
|
></p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</SpoiledWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FilePath({ file }: { file: TelegramFile }) {
|
||||||
const [, copyToClipboard] = useCopyToClipboard();
|
const [, copyToClipboard] = useCopyToClipboard();
|
||||||
|
|
||||||
|
if (!file.localPath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<div className="flex items-center gap-2 text-sm">
|
||||||
|
<FileCheck className="h-4 w-4 flex-shrink-0" />
|
||||||
|
<p
|
||||||
|
className="group line-clamp-1 cursor-pointer overflow-hidden truncate text-ellipsis text-wrap rounded px-1 hover:bg-gray-100"
|
||||||
|
onClick={() => copyToClipboard(file.localPath)}
|
||||||
|
>
|
||||||
|
{file.localPath.split("/").pop()}
|
||||||
|
<Copy className="ml-1 inline-flex h-4 w-4 opacity-0 group-hover:opacity-100" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<div className="max-w-80 text-wrap rounded p-2">{file.localPath}</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileTime({ file }: { file: TelegramFile }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<Clock className="h-4 w-4" />
|
||||||
|
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
|
||||||
|
{formatDistanceToNow(new Date(file.date * 1000), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<div className="max-w-80 text-wrap rounded p-2">
|
||||||
|
{`Message received at ${new Date(file.date * 1000).toLocaleString()}`}
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
{file.completionDate && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<ClockArrowDown className="h-4 w-4" />
|
||||||
|
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
|
||||||
|
{formatDistanceToNow(new Date(file.completionDate), {
|
||||||
|
addSuffix: true,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<div className="max-w-80 text-wrap rounded p-2">
|
||||||
|
{`File downloaded at ${new Date(file.completionDate).toLocaleString()}`}
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FileExtra({ file, rowHeight }: FileExtraProps) {
|
||||||
|
if (rowHeight === "s") {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
{file.fileName ? <FileName file={file} /> : <FileTime file={file} />}
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col space-y-1 overflow-hidden">
|
<div className="relative flex flex-col space-y-1 overflow-hidden">
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
{file.fileName && (
|
<FileName file={file} />
|
||||||
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
|
<FileCaption file={file} rowHeight={rowHeight} />
|
||||||
<p className="flex items-center gap-2">
|
<FilePath file={file} />
|
||||||
<Mountain className="h-4 w-4" />
|
<FileTime file={file} />
|
||||||
<span className="rounded px-1 text-sm hover:bg-gray-100">
|
|
||||||
{file.fileName}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</SpoiledWrapper>
|
|
||||||
)}
|
|
||||||
{rowHeight !== "s" && file.caption && (
|
|
||||||
<SpoiledWrapper hasSensitiveContent={file.hasSensitiveContent}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Captions className="h-4 w-4 flex-shrink-0" />
|
|
||||||
<p
|
|
||||||
className={cn(
|
|
||||||
rowHeight !== "l" && "line-clamp-1",
|
|
||||||
"overflow-hidden truncate text-ellipsis text-wrap text-start text-sm",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{file.caption}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p
|
|
||||||
className="max-w-80 text-wrap rounded p-2"
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: file.caption.replaceAll("\n", "<br />"),
|
|
||||||
}}
|
|
||||||
></p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</SpoiledWrapper>
|
|
||||||
)}
|
|
||||||
{rowHeight !== "s" && file.localPath && (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
|
||||||
<FileCheck className="h-4 w-4 flex-shrink-0" />
|
|
||||||
<p
|
|
||||||
className="group line-clamp-1 cursor-pointer overflow-hidden truncate text-ellipsis text-wrap rounded px-1 hover:bg-gray-100"
|
|
||||||
onClick={() => copyToClipboard(file.localPath)}
|
|
||||||
>
|
|
||||||
{file.localPath.split("/").pop()}
|
|
||||||
<Copy className="ml-1 inline-flex h-4 w-4 opacity-0 group-hover:opacity-100" />
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<div className="max-w-80 text-wrap rounded p-2">
|
|
||||||
{file.localPath}
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{rowHeight !== "s" && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<p className="flex items-center gap-2">
|
|
||||||
<Clock className="h-4 w-4" />
|
|
||||||
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
|
|
||||||
{formatDistanceToNow(new Date(file.date * 1000), {
|
|
||||||
addSuffix: true,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<div className="max-w-80 text-wrap rounded p-2">
|
|
||||||
{`Message received at ${new Date(file.date * 1000).toLocaleString()}`}
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
{file.completionDate && (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<p className="flex items-center gap-2">
|
|
||||||
<ClockArrowDown className="h-4 w-4" />
|
|
||||||
<span className="rounded px-1 text-sm text-muted-foreground hover:bg-gray-100">
|
|
||||||
{formatDistanceToNow(new Date(file.completionDate), {
|
|
||||||
addSuffix: true,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<div className="max-w-80 text-wrap rounded p-2">
|
|
||||||
{`File downloaded at ${new Date(file.completionDate).toLocaleString()}`}
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,14 @@ export function useFileSpeed(fileId: number) {
|
||||||
lastTimestamp: 0,
|
lastTimestamp: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [debounceSpeed] = useDebounce(downloadSpeed.speed, 1000, {
|
const [debounceSpeed] = useDebounce(downloadSpeed.speed, 300, {
|
||||||
leading: true,
|
leading: true,
|
||||||
maxWait: 2000,
|
maxWait: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [debounceProgress] = useDebounce(downloadProgress, 300, {
|
||||||
|
leading: true,
|
||||||
|
maxWait: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -94,7 +99,7 @@ export function useFileSpeed(fileId: number) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
downloadProgress,
|
downloadProgress: debounceProgress,
|
||||||
downloadSpeed: debounceSpeed,
|
downloadSpeed: debounceSpeed,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import useSWRInfinite from "swr/infinite";
|
||||||
import { useWebsocket } from "@/hooks/use-websocket";
|
import { useWebsocket } from "@/hooks/use-websocket";
|
||||||
import { WebSocketMessageType } from "@/lib/websocket-types";
|
import { WebSocketMessageType } from "@/lib/websocket-types";
|
||||||
import useLocalStorage from "@/hooks/use-local-storage";
|
import useLocalStorage from "@/hooks/use-local-storage";
|
||||||
import {useDebounce} from "use-debounce";
|
import { useDebounce } from "use-debounce";
|
||||||
|
|
||||||
const DEFAULT_FILTERS: FileFilter = {
|
const DEFAULT_FILTERS: FileFilter = {
|
||||||
search: "",
|
search: "",
|
||||||
|
|
@ -31,6 +31,7 @@ export function useFiles(accountId: string, chatId: string) {
|
||||||
downloadStatus: DownloadStatus;
|
downloadStatus: DownloadStatus;
|
||||||
localPath: string;
|
localPath: string;
|
||||||
completionDate: number;
|
completionDate: number;
|
||||||
|
downloadedSize: number;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>({});
|
>({});
|
||||||
|
|
@ -76,6 +77,7 @@ export function useFiles(accountId: string, chatId: string) {
|
||||||
downloadStatus: DownloadStatus;
|
downloadStatus: DownloadStatus;
|
||||||
localPath: string;
|
localPath: string;
|
||||||
completionDate: number;
|
completionDate: number;
|
||||||
|
downloadedSize: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
setLatestFileStatus((prev) => ({
|
setLatestFileStatus((prev) => ({
|
||||||
|
|
@ -84,7 +86,10 @@ export function useFiles(accountId: string, chatId: string) {
|
||||||
downloadStatus:
|
downloadStatus:
|
||||||
data.downloadStatus ?? prev[data.fileId]?.downloadStatus,
|
data.downloadStatus ?? prev[data.fileId]?.downloadStatus,
|
||||||
localPath: data.localPath ?? prev[data.fileId]?.localPath,
|
localPath: data.localPath ?? prev[data.fileId]?.localPath,
|
||||||
completionDate: data.completionDate ?? prev[data.fileId]?.completionDate,
|
completionDate:
|
||||||
|
data.completionDate ?? prev[data.fileId]?.completionDate,
|
||||||
|
downloadedSize:
|
||||||
|
data.downloadedSize ?? prev[data.fileId]?.downloadedSize,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}, [lastJsonMessage]);
|
}, [lastJsonMessage]);
|
||||||
|
|
@ -101,6 +106,8 @@ export function useFiles(accountId: string, chatId: string) {
|
||||||
localPath: latestFilesStatus[file.id]?.localPath ?? file.localPath,
|
localPath: latestFilesStatus[file.id]?.localPath ?? file.localPath,
|
||||||
completionDate:
|
completionDate:
|
||||||
latestFilesStatus[file.id]?.completionDate ?? file.completionDate,
|
latestFilesStatus[file.id]?.completionDate ?? file.completionDate,
|
||||||
|
downloadedSize:
|
||||||
|
latestFilesStatus[file.id]?.downloadedSize ?? file.downloadedSize,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
import { useToast } from "./use-toast";
|
import { useToast } from "./use-toast";
|
||||||
import { useDebounce } from "use-debounce";
|
import { useDebounce } from "use-debounce";
|
||||||
import { getWsUrl } from "@/lib/api";
|
import { getWsUrl } from "@/lib/api";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
const WS_URL = `${getWsUrl()}`;
|
const WS_URL = `${getWsUrl()}`;
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ interface WebSocketProviderProps {
|
||||||
export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
|
export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
const params = useParams();
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const [accountDownloadSpeed, setAccountDownloadSpeed] = useState({
|
const [accountDownloadSpeed, setAccountDownloadSpeed] = useState({
|
||||||
speed: 0,
|
speed: 0,
|
||||||
|
|
@ -46,17 +48,20 @@ export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
|
||||||
lastTimestamp: 0,
|
lastTimestamp: 0,
|
||||||
});
|
});
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [debounceSpeed] = useDebounce(accountDownloadSpeed.speed, 1000, {
|
const [debounceSpeed] = useDebounce(accountDownloadSpeed.speed, 300, {
|
||||||
leading: true,
|
leading: true,
|
||||||
maxWait: 2000,
|
maxWait: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { sendMessage, lastJsonMessage, readyState } =
|
const { sendMessage, lastJsonMessage, readyState } =
|
||||||
useWebSocket<WebSocketMessage>(WS_URL, {
|
useWebSocket<WebSocketMessage>(
|
||||||
shouldReconnect: (closeEvent) => true,
|
`${WS_URL}?telegramId=${(params.accountId as string) ?? ""}`,
|
||||||
reconnectAttempts: 3,
|
{
|
||||||
reconnectInterval: 3000,
|
shouldReconnect: (closeEvent) => true,
|
||||||
});
|
reconnectAttempts: 3,
|
||||||
|
reconnectInterval: 3000,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const connectionStatus = {
|
const connectionStatus = {
|
||||||
[ReadyState.CONNECTING]: "Connecting",
|
[ReadyState.CONNECTING]: "Connecting",
|
||||||
|
|
@ -106,7 +111,7 @@ export const WebSocketProvider: React.FC<WebSocketProviderProps> = ({
|
||||||
lastDownloadedSize: downloadedSize,
|
lastDownloadedSize: downloadedSize,
|
||||||
};
|
};
|
||||||
const timeDiff = (timestamp - prev.lastTimestamp) / 1000;
|
const timeDiff = (timestamp - prev.lastTimestamp) / 1000;
|
||||||
if (prev.lastTimestamp === 0 || timeDiff <= 0) {
|
if (prev.lastTimestamp === 0 || timeDiff <= 0 || downloadedSize <= prev.lastDownloadedSize) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
speed: prev.speed,
|
speed: prev.speed,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue