🧲 Surfacing torrent progress in UI via scheduled job
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user