librqbit 9.0.1

The main library used by rqbit torrent client. The binary is just a small wrapper on top of it.
Documentation
import {
  TorrentListItem,
  STATE_INITIALIZING,
  STATE_LIVE,
  STATE_PAUSED,
} from "../../api-types";
import { formatBytes } from "../../helper/formatBytes";
import { getCompletionETA } from "../../helper/getCompletionETA";
import { PlaylistLink } from "../buttons/PlaylistButton";
import { PiecesCanvas } from "./PiecesCanvas";

interface OverviewTabProps {
  torrent: TorrentListItem | null;
}

// Labeled value - the building block for all stats
const LV: React.FC<{
  label: string;
  value: React.ReactNode;
  mono?: boolean;
}> = ({ label, value, mono }) => (
  <>
    <span className="text-tertiary">{label} </span>
    <span className={mono ? "font-mono" : ""}>{value}</span>
  </>
);

export const OverviewTab: React.FC<OverviewTabProps> = ({ torrent }) => {
  const statsResponse = torrent?.stats ?? null;

  if (!torrent || !statsResponse) {
    return <div className="p-3 text-tertiary">Loading...</div>;
  }

  const name = torrent.name ?? "";
  const infoHash = torrent.info_hash;
  const state = statsResponse.state;
  const error = statsResponse.error;
  const totalBytes = statsResponse.total_bytes ?? 1;
  const progressBytes = statsResponse.progress_bytes ?? 0;
  const finished = statsResponse.finished || false;
  const paused = statsResponse.initializing_paused ?? false;

  const totalPieces = torrent.total_pieces ?? 0;
  const downloadedPieces =
    statsResponse.live?.snapshot.downloaded_and_checked_pieces ?? 0;
  const pieceSize = totalPieces > 0 ? totalBytes / totalPieces : 0;

  const totalUploadedBytes = statsResponse.live?.snapshot.uploaded_bytes ?? 0;

  const progressPct = error
    ? 100
    : totalBytes === 0
      ? 100
      : (progressBytes / totalBytes) * 100;

  const downSpeed = statsResponse.live?.download_speed?.human_readable ?? "-";
  const upSpeed = statsResponse.live?.upload_speed?.human_readable ?? "-";
  const eta = getCompletionETA(statsResponse);

  const peers = statsResponse.live?.snapshot.peer_stats;

  const stateDisplay = (() => {
    if (error) return { text: "Error", color: "text-error" };
    if (state === STATE_INITIALIZING)
      return {
        text: paused ? "Initializing (paused)" : "Initializing",
        color: "text-warning",
      };
    if (state === STATE_PAUSED)
      return { text: "Paused", color: "text-secondary" };
    if (state === STATE_LIVE && finished)
      return { text: "Seeding", color: "text-success" };
    if (state === STATE_LIVE)
      return { text: "Downloading", color: "text-primary" };
    return { text: state, color: "text-secondary" };
  })();

  return (
    <div className="p-3 flex flex-col gap-3 text-sm">
      {/* Header: Name + Status */}
      <div className="flex items-center gap-3">
        <span className="truncate font-medium flex-1" title={name}>
          {name}
        </span>
        <span className={`shrink-0 ${stateDisplay.color}`}>
          {stateDisplay.text}
        </span>
      </div>
      {/* Pieces visualization */}
      {totalPieces > 0 && (
        <div>
          <PiecesCanvas
            torrentId={torrent.id}
            totalPieces={totalPieces}
            stats={statsResponse}
          />
        </div>
      )}

      {/* Main stats line */}
      <div className="flex flex-wrap gap-x-4 gap-y-1">
        <span>
          <LV label="Progress" value={`${progressPct.toFixed(1)}%`} />
          <span className="text-tertiary">
            {" "}
            ({formatBytes(progressBytes)}/{formatBytes(totalBytes)})
          </span>
        </span>
        <span>
          <LV label="Down" value={downSpeed} />
        </span>
        <span>
          <LV label="Up" value={upSpeed} />
          <span className="text-tertiary">
            {" "}
            ({formatBytes(totalUploadedBytes)} total)
          </span>
        </span>
        <span>
          <LV label="ETA" value={finished ? "Complete" : eta} />
        </span>
      </div>

      {/* Pieces + Peers line */}
      <div className="flex flex-wrap gap-x-4 gap-y-1">
        {totalPieces > 0 && (
          <span>
            <LV
              label="Pieces"
              value={`${downloadedPieces.toLocaleString()}/${totalPieces.toLocaleString()}`}
            />
            {pieceSize > 0 && (
              <span className="text-tertiary">
                {" "}
                ({formatBytes(pieceSize)} each)
              </span>
            )}
          </span>
        )}
        {peers && (
          <span>
            <LV label="Peers" value="" />
            <span className="text-success">{peers.live}</span>
            <span className="text-tertiary"> live, </span>
            <span className="text-primary">{peers.connecting}</span>
            <span className="text-tertiary"> connecting, </span>
            <span className="text-warning">{peers.queued}</span>
            <span className="text-tertiary"> queued, </span>
            <span>{peers.seen}</span>
            <span className="text-tertiary"> seen, </span>
            <span className="text-error">{peers.dead}</span>
            <span className="text-tertiary"> dead</span>
          </span>
        )}
      </div>

      {/* Metadata */}
      <div className="flex flex-col gap-1">
        <div className="truncate">
          <LV label="Hash" value={infoHash} mono />
        </div>
        <div className="truncate">
          <LV label="Output" value={torrent.output_folder} mono />
        </div>
        <div className="truncate">
          <LV
            label="Playlist"
            value={<PlaylistLink torrentId={torrent.id} />}
          />
        </div>
      </div>

      {/* Error */}
      {error && <div className="text-error">{error}</div>}
    </div>
  );
};