mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
refactor(ai): extract updateTorrentStatus from js/app.js (agent)
Moved torrent status update logic to `js/ui/torrentStatusController.js` to improve separation of concerns as per `AGENTS.md`. The controller accepts a `getVideoModal` accessor and an `onRemovePoster` callback to decouple it from direct `videoModal` or `Application` dependencies. Added unit tests in `tests/ui/torrentStatusController.test.mjs`.
This commit is contained in:
1 parent
dedc2aefa5
commit
45b20bb58e
3 files changed
+147
-45
No files matched your search
@@ -144,6 +144,7 @@ import {
|
||||
} from "./state/cache.js";
|
||||
import ApplicationBootstrap from "./ui/applicationBootstrap.js";
|
||||
import VideoModalCommentController from "./ui/videoModalCommentController.js";
|
||||
import TorrentStatusController from "./ui/torrentStatusController.js";
|
||||
import ModerationActionController from "./services/moderationActionController.js";
|
||||
import { bootstrapTrustedSeeds } from "./services/trustBootstrap.js";
|
||||
|
||||
@@ -223,6 +224,11 @@ class Application {
|
||||
this.commentController = null;
|
||||
this.initializeCommentController();
|
||||
|
||||
this.torrentStatusController = new TorrentStatusController({
|
||||
getVideoModal: () => this.videoModal,
|
||||
onRemovePoster: (reason) => this.forceRemoveModalPoster(reason),
|
||||
});
|
||||
|
||||
this.unsubscribeFromPubkeyState = subscribeToAppStateKey(
|
||||
"pubkey",
|
||||
(next, previous) => {
|
||||
@@ -7482,51 +7488,8 @@ class Application {
|
||||
* and do not re-trigger recursion here (no setTimeout).
|
||||
*/
|
||||
updateTorrentStatus(torrent) {
|
||||
devLogger.log("[DEBUG] updateTorrentStatus called with torrent:", torrent);
|
||||
|
||||
if (!torrent) {
|
||||
devLogger.log("[DEBUG] torrent is null/undefined!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (torrent.ready || (typeof torrent.progress === "number" && torrent.progress > 0)) {
|
||||
// Belt-and-suspenders: if WebTorrent reports progress but the DOM events
|
||||
// failed to fire we still rip off the loading GIF. This regression has
|
||||
// bitten us in past releases, so the extra clear is intentional.
|
||||
this.forceRemoveModalPoster(
|
||||
torrent.ready ? "torrent-ready-flag" : "torrent-progress"
|
||||
);
|
||||
}
|
||||
|
||||
// Log only fields that actually exist on the torrent:
|
||||
devLogger.log("[DEBUG] torrent.progress =", torrent.progress);
|
||||
devLogger.log("[DEBUG] torrent.numPeers =", torrent.numPeers);
|
||||
devLogger.log("[DEBUG] torrent.downloadSpeed =", torrent.downloadSpeed);
|
||||
devLogger.log("[DEBUG] torrent.downloaded =", torrent.downloaded);
|
||||
devLogger.log("[DEBUG] torrent.length =", torrent.length);
|
||||
devLogger.log("[DEBUG] torrent.ready =", torrent.ready);
|
||||
|
||||
// Use "Complete" vs. "Downloading" as the textual status.
|
||||
if (this.videoModal) {
|
||||
const fullyDownloaded = torrent.progress >= 1;
|
||||
this.videoModal.updateStatus(fullyDownloaded ? "Complete" : "Downloading");
|
||||
|
||||
const percent = (torrent.progress * 100).toFixed(2);
|
||||
this.videoModal.updateProgress(`${percent}%`);
|
||||
this.videoModal.updatePeers(`Peers: ${torrent.numPeers}`);
|
||||
|
||||
const kb = (torrent.downloadSpeed / 1024).toFixed(2);
|
||||
this.videoModal.updateSpeed(`${kb} KB/s`);
|
||||
|
||||
const downloadedMb = (torrent.downloaded / (1024 * 1024)).toFixed(2);
|
||||
const lengthMb = (torrent.length / (1024 * 1024)).toFixed(2);
|
||||
this.videoModal.updateDownloaded(
|
||||
`${downloadedMb} MB / ${lengthMb} MB`
|
||||
);
|
||||
|
||||
if (torrent.ready) {
|
||||
this.videoModal.updateStatus("Ready to play");
|
||||
}
|
||||
if (this.torrentStatusController) {
|
||||
this.torrentStatusController.update(torrent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { devLogger } from "../utils/logger.js";
|
||||
|
||||
export default class TorrentStatusController {
|
||||
constructor({ getVideoModal, onRemovePoster }) {
|
||||
if (typeof getVideoModal !== "function") {
|
||||
throw new Error("TorrentStatusController requires a getVideoModal accessor.");
|
||||
}
|
||||
this.getVideoModal = getVideoModal;
|
||||
this.onRemovePoster = typeof onRemovePoster === "function" ? onRemovePoster : () => {};
|
||||
}
|
||||
|
||||
update(torrent) {
|
||||
devLogger.log("[DEBUG] TorrentStatusController.update called with torrent:", torrent);
|
||||
|
||||
if (!torrent) {
|
||||
devLogger.log("[DEBUG] torrent is null/undefined!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (torrent.ready || (typeof torrent.progress === "number" && torrent.progress > 0)) {
|
||||
// Belt-and-suspenders: if WebTorrent reports progress but the DOM events
|
||||
// failed to fire we still rip off the loading GIF. This regression has
|
||||
// bitten us in past releases, so the extra clear is intentional.
|
||||
this.onRemovePoster(
|
||||
torrent.ready ? "torrent-ready-flag" : "torrent-progress"
|
||||
);
|
||||
}
|
||||
|
||||
// Log only fields that actually exist on the torrent:
|
||||
devLogger.log("[DEBUG] torrent.progress =", torrent.progress);
|
||||
devLogger.log("[DEBUG] torrent.numPeers =", torrent.numPeers);
|
||||
devLogger.log("[DEBUG] torrent.downloadSpeed =", torrent.downloadSpeed);
|
||||
devLogger.log("[DEBUG] torrent.downloaded =", torrent.downloaded);
|
||||
devLogger.log("[DEBUG] torrent.length =", torrent.length);
|
||||
devLogger.log("[DEBUG] torrent.ready =", torrent.ready);
|
||||
|
||||
const videoModal = this.getVideoModal();
|
||||
|
||||
// Use "Complete" vs. "Downloading" as the textual status.
|
||||
if (videoModal) {
|
||||
const fullyDownloaded = torrent.progress >= 1;
|
||||
if (typeof videoModal.updateStatus === "function") {
|
||||
videoModal.updateStatus(fullyDownloaded ? "Complete" : "Downloading");
|
||||
}
|
||||
|
||||
if (typeof videoModal.updateProgress === "function") {
|
||||
const percent = (torrent.progress * 100).toFixed(2);
|
||||
videoModal.updateProgress(`${percent}%`);
|
||||
}
|
||||
|
||||
if (typeof videoModal.updatePeers === "function") {
|
||||
videoModal.updatePeers(`Peers: ${torrent.numPeers}`);
|
||||
}
|
||||
|
||||
if (typeof videoModal.updateSpeed === "function") {
|
||||
const kb = (torrent.downloadSpeed / 1024).toFixed(2);
|
||||
videoModal.updateSpeed(`${kb} KB/s`);
|
||||
}
|
||||
|
||||
if (typeof videoModal.updateDownloaded === "function") {
|
||||
const downloadedMb = (torrent.downloaded / (1024 * 1024)).toFixed(2);
|
||||
const lengthMb = (torrent.length / (1024 * 1024)).toFixed(2);
|
||||
videoModal.updateDownloaded(
|
||||
`${downloadedMb} MB / ${lengthMb} MB`
|
||||
);
|
||||
}
|
||||
|
||||
if (torrent.ready && typeof videoModal.updateStatus === "function") {
|
||||
videoModal.updateStatus("Ready to play");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import TorrentStatusController from "../../js/ui/torrentStatusController.js";
|
||||
|
||||
test("TorrentStatusController throws if accessor is missing", (t) => {
|
||||
assert.throws(() => new TorrentStatusController({}), /accessor/);
|
||||
});
|
||||
|
||||
test("TorrentStatusController updates video modal and calls onRemovePoster", (t) => {
|
||||
let modalState = {};
|
||||
let posterRemoved = false;
|
||||
|
||||
const mockModal = {
|
||||
updateStatus: (msg) => { modalState.status = msg; },
|
||||
updateProgress: (msg) => { modalState.progress = msg; },
|
||||
updatePeers: (msg) => { modalState.peers = msg; },
|
||||
updateSpeed: (msg) => { modalState.speed = msg; },
|
||||
updateDownloaded: (msg) => { modalState.downloaded = msg; },
|
||||
};
|
||||
|
||||
const onRemovePoster = (reason) => {
|
||||
posterRemoved = reason;
|
||||
};
|
||||
|
||||
const controller = new TorrentStatusController({
|
||||
getVideoModal: () => mockModal,
|
||||
onRemovePoster
|
||||
});
|
||||
|
||||
// Test basic update
|
||||
controller.update({
|
||||
ready: false,
|
||||
progress: 0.5,
|
||||
numPeers: 5,
|
||||
downloadSpeed: 1024 * 100, // 100 KB/s
|
||||
downloaded: 1024 * 1024 * 10, // 10 MB
|
||||
length: 1024 * 1024 * 100 // 100 MB
|
||||
});
|
||||
|
||||
assert.equal(modalState.status, "Downloading");
|
||||
assert.equal(modalState.progress, "50.00%");
|
||||
assert.equal(modalState.peers, "Peers: 5");
|
||||
assert.equal(modalState.speed, "100.00 KB/s");
|
||||
assert.equal(modalState.downloaded, "10.00 MB / 100.00 MB");
|
||||
assert.equal(posterRemoved, "torrent-progress");
|
||||
|
||||
// Test complete
|
||||
controller.update({
|
||||
ready: true,
|
||||
progress: 1,
|
||||
numPeers: 0,
|
||||
downloadSpeed: 0,
|
||||
downloaded: 1024 * 1024 * 100,
|
||||
length: 1024 * 1024 * 100
|
||||
});
|
||||
|
||||
// It sets "Complete" then "Ready to play" if ready is true
|
||||
assert.equal(modalState.status, "Ready to play");
|
||||
assert.equal(modalState.progress, "100.00%");
|
||||
assert.equal(posterRemoved, "torrent-ready-flag");
|
||||
});
|
||||
|
||||
test("TorrentStatusController handles missing modal gracefully", (t) => {
|
||||
const controller = new TorrentStatusController({ getVideoModal: () => null });
|
||||
assert.doesNotThrow(() => controller.update({ progress: 0.5 }));
|
||||
});
|
||||
Reference in new issue
Block a user