🧲 Surfacing torrent progress in UI via scheduled job

This commit is contained in:
2024-03-27 22:23:24 -05:00
parent 173735da45
commit 56ddfbd16e
2 changed files with 57 additions and 38 deletions

View File

@@ -8,6 +8,7 @@ import axios from "axios";
import { import {
LIBRARY_SERVICE_BASE_URI, LIBRARY_SERVICE_BASE_URI,
QBITTORRENT_SERVICE_BASE_URI, QBITTORRENT_SERVICE_BASE_URI,
JOB_QUEUE_SERVICE_BASE_URI,
} from "../../constants/endpoints"; } from "../../constants/endpoints";
import { useStore } from "../../store"; import { useStore } from "../../store";
import { useShallow } from "zustand/react/shallow"; import { useShallow } from "zustand/react/shallow";
@@ -23,13 +24,26 @@ export const DownloadsPanel = (
const { comicObjectId } = useParams<{ comicObjectId: string }>(); const { comicObjectId } = useParams<{ comicObjectId: string }>();
const [bundles, setBundles] = useState([]); const [bundles, setBundles] = useState([]);
const [infoHashes, setInfoHashes] = useState<string[]>([]); const [infoHashes, setInfoHashes] = useState<string[]>([]);
const [torrentDetails, setTorrentDetails] = useState([]);
const [activeTab, setActiveTab] = useState("torrents"); const [activeTab, setActiveTab] = useState("torrents");
const { airDCPPSocketInstance } = useStore( const { airDCPPSocketInstance, socketIOInstance } = useStore(
useShallow((state) => ({ useShallow((state: any) => ({
airDCPPSocketInstance: state.airDCPPSocketInstance, airDCPPSocketInstance: state.airDCPPSocketInstance,
socketIOInstance: state.socketIOInstance,
})), })),
); );
// React to torrent progress data sent over websockets
socketIOInstance.on("AS_TORRENT_DATA", (data) => {
const torrents = data.torrents
.flatMap(({ _id, details }) => {
if (_id === comicObjectId) {
return details;
}
})
.filter((item) => item !== undefined);
setTorrentDetails(torrents);
});
// Fetch the downloaded files and currently-downloading file(s) from AirDC++ // Fetch the downloaded files and currently-downloading file(s) from AirDC++
const { data: comicObject, isSuccess } = useQuery({ const { data: comicObject, isSuccess } = useQuery({
queryKey: ["bundles"], queryKey: ["bundles"],
@@ -46,19 +60,19 @@ export const DownloadsPanel = (
}), }),
}); });
const { // const {
data: torrentProperties, // data: torrentProperties,
isSuccess: torrentPropertiesFetched, // isSuccess: torrentPropertiesFetched,
isFetching: torrentPropertiesFetching, // isFetching: torrentPropertiesFetching,
} = useQuery({ // } = useQuery({
queryFn: async () => // queryFn: async () =>
await axios({ // await axios({
url: `${QBITTORRENT_SERVICE_BASE_URI}/getTorrentProperties`, // url: `${QBITTORRENT_SERVICE_BASE_URI}/getTorrentProperties`,
method: "POST", // method: "POST",
data: { infoHashes }, // data: { infoHashes },
}), // }),
queryKey: ["torrentProperties", infoHashes], // queryKey: ["torrentProperties", infoHashes],
}); // });
const getBundles = async (comicObject) => { const getBundles = async (comicObject) => {
if (comicObject?.data.acquisition.directconnect) { if (comicObject?.data.acquisition.directconnect) {
@@ -72,26 +86,24 @@ export const DownloadsPanel = (
} }
}; };
// Call the scheduled job for fetching torrent data
// triggered by the active tab been set to "torrents"
const { data: torrentData } = useQuery({
queryFn: () =>
axios({
url: `${JOB_QUEUE_SERVICE_BASE_URI}/getTorrentData`,
method: "GET",
params: {
trigger: activeTab,
},
}),
queryKey: [activeTab],
});
useEffect(() => { useEffect(() => {
getBundles(comicObject).then((result) => { getBundles(comicObject).then((result) => {
setBundles(result); setBundles(result);
}); });
if (comicObject?.data.acquisition.torrent.length !== 0) {
// Use the functional form of setInfoHashes to avoid race conditions
setInfoHashes(() => {
// Extract infoHashes from torrents and remove duplicates
const newInfoHashes: any = [
...new Set(
comicObject?.data.acquisition.torrent.map(
(torrent) => torrent.infoHash,
),
),
];
console.log(infoHashes);
return newInfoHashes;
});
}
}, [comicObject]); }, [comicObject]);
return ( return (
@@ -116,7 +128,11 @@ export const DownloadsPanel = (
<nav className="flex gap-6" aria-label="Tabs"> <nav className="flex gap-6" aria-label="Tabs">
<a <a
href="#" href="#"
className="shrink-0 rounded-lg p-2 text-sm font-medium text-slate-200 hover:bg-gray-50 hover:text-gray-700" className={`shrink-0 rounded-lg p-2 text-sm font-medium hover:bg-gray-50 hover:text-gray-700 ${
activeTab === "directconnect"
? "bg-slate-200 text-slate-800"
: "text-slate-200"
}`}
aria-current="page" aria-current="page"
onClick={() => setActiveTab("directconnect")} onClick={() => setActiveTab("directconnect")}
> >
@@ -125,7 +141,11 @@ export const DownloadsPanel = (
<a <a
href="#" href="#"
className="shrink-0 rounded-lg p-2 text-sm font-medium text-slate-200 hover:bg-gray-50 hover:text-gray-700" className={`shrink-0 rounded-lg p-2 text-sm font-medium hover:bg-gray-50 hover:text-gray-700 ${
activeTab === "torrents"
? "bg-slate-200 text-slate-800"
: "text-slate-200"
}`}
onClick={() => setActiveTab("torrents")} onClick={() => setActiveTab("torrents")}
> >
Torrents Torrents
@@ -134,9 +154,7 @@ export const DownloadsPanel = (
</div> </div>
</div> </div>
{activeTab === "torrents" && torrentPropertiesFetched && ( {activeTab === "torrents" && <TorrentDownloads data={torrentDetails} />}
<TorrentDownloads data={torrentProperties?.data} />
)}
</div> </div>
); );
}; };

View File

@@ -4,10 +4,10 @@ import prettyBytes from "pretty-bytes";
export const TorrentDownloads = (props) => { export const TorrentDownloads = (props) => {
const { data } = props; const { data } = props;
console.log(data); console.log(Object.values(data));
return ( return (
<> <>
{data.map((torrent) => { {data.map(({ torrent }) => {
return ( return (
<dl> <dl>
<dt className="text-lg">{torrent.name}</dt> <dt className="text-lg">{torrent.name}</dt>
@@ -15,6 +15,7 @@ export const TorrentDownloads = (props) => {
<p className="text-sm"> <p className="text-sm">
Added on {dayjs.unix(torrent.added_on).format("ddd, D MMM, YYYY")} Added on {dayjs.unix(torrent.added_on).format("ddd, D MMM, YYYY")}
</p> </p>
<p>{torrent.progress}</p>
<div className="flex gap-4 mt-2"> <div className="flex gap-4 mt-2">
{/* Peers */} {/* Peers */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400"> <span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">