From 21a127509b948abe10262745bd7034193c29cdf8 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Mon, 17 Feb 2025 16:27:48 -0500 Subject: [PATCH] Airdcpp regression (#123) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔧 Fixing broken AirDCPP search * 🔧 Fixing broken DC++ downloads * 🔧 Todo to move method out to core service * 🔧 Fixing the DC++ download bundles * 🔧 Bundles endpoint integration * 🔧 Fixed the download bundles page * ➕ Added an active hub badge to DC++ search * 🔧 Fixing autodownload functionality * 🔧 Fixed PullList source --------- Signed-off-by: Rishi Ghan --- .../ComicDetail/AcquisitionPanel.tsx | 286 +++++++++--------- .../AsyncSelectPaginate.tsx | 17 +- .../ComicDetail/ComicVineDetails.tsx | 23 +- .../components/ComicDetail/DownloadsPanel.tsx | 63 ++-- .../components/ComicDetail/RawFileDetails.tsx | 57 ++-- .../ComicDetail/TorrentSearchPanel.tsx | 2 +- src/client/components/Dashboard/PullList.tsx | 8 +- src/client/components/shared/Carda.tsx | 2 +- src/client/components/shared/T2Table.tsx | 26 +- src/client/shared/utils/metadata.utils.ts | 4 +- yarn.lock | 46 +-- 11 files changed, 268 insertions(+), 266 deletions(-) diff --git a/src/client/components/ComicDetail/AcquisitionPanel.tsx b/src/client/components/ComicDetail/AcquisitionPanel.tsx index e27e753..d592f30 100644 --- a/src/client/components/ComicDetail/AcquisitionPanel.tsx +++ b/src/client/components/ComicDetail/AcquisitionPanel.tsx @@ -1,5 +1,4 @@ import React, { useCallback, ReactElement, useEffect, useState } from "react"; -import { getBundlesForComic, sleep } from "../../actions/airdcpp.actions"; import { SearchQuery, PriorityEnum, SearchResponse } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings"; import ellipsize from "ellipsize"; @@ -12,7 +11,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import axios from "axios"; import { AIRDCPP_SERVICE_BASE_URI } from "../../constants/endpoints"; - interface IAcquisitionPanelProps { query: any; comicObjectId: any; @@ -35,18 +33,20 @@ export const AcquisitionPanel = ( priority: PriorityEnum; } interface SearchResult { - result: { - id: number; - }; - search_id: number; + id: string; // Add other properties as needed + slots: any; + type: any; + users: any; + name: string; + dupe: Boolean; + size: number; } const handleSearch = (searchQuery) => { // Use the already connected socket instance to emit events socketIOInstance.emit("initiateSearch", searchQuery); }; - const { data: settings, isLoading, @@ -110,43 +110,36 @@ export const AcquisitionPanel = ( */ const search = async (searchData: any) => { setAirDCPPSearchResults([]); - socketIOInstance.emit( - "call", - "socket.search", - { - query: searchData, - config: { - protocol: `ws`, - hostname: `localhost:5600`, - username: `user`, - password: `pass`, - }, + socketIOInstance.emit("call", "socket.search", { + query: searchData, + config: { + protocol: `ws`, + // hostname: `192.168.1.119:5600`, + hostname: `127.0.0.1:5600`, + username: `user`, + password: `pass`, }, - (data: any) => console.log(data), - ); + }); }; - socketIOInstance.on("searchResultAdded", ({ groupedResult }: any) => { + socketIOInstance.on("searchResultAdded", ({ result }: any) => { setAirDCPPSearchResults((previousState) => { - const exists = previousState.some( - (item) => groupedResult.result.id === item.result.id, - ); + const exists = previousState.some((item) => result.id === item.id); if (!exists) { - return [...previousState, groupedResult]; + return [...previousState, result]; } return previousState; }); }); - socketIOInstance.on("searchResultUpdated", ({ updatedResult }: any) => { - console.log("endh"); + socketIOInstance.on("searchResultUpdated", ({ result }: any) => { // ...update properties of the existing result in the UI const bundleToUpdateIndex = airDCPPSearchResults?.findIndex( - (bundle) => bundle.result.id === updatedResult.result.id, + (bundle) => bundle.id === result.id, ); const updatedState = [...airDCPPSearchResults]; - if (!isNil(difference(updatedState[bundleToUpdateIndex], updatedResult))) { - updatedState[bundleToUpdateIndex] = updatedResult; + if (!isNil(difference(updatedState[bundleToUpdateIndex], result))) { + updatedState[bundleToUpdateIndex] = result; } setAirDCPPSearchResults((state) => [...state, ...updatedState]); }); @@ -177,7 +170,7 @@ export const AcquisitionPanel = ( size: Number, type: any, config: any, - ): void => { + ): Promise => { socketIOInstance.emit( "call", "socket.download", @@ -199,7 +192,7 @@ export const AcquisitionPanel = ( pattern: `${searchQuery.issueName}`, extensions: ["cbz", "cbr", "cb7"], }, - hub_urls: map(hubs?.data, (hub) => hub.hub_url), + hub_urls: [hubs?.data[0].hub_url], priority: 5, }; @@ -208,7 +201,7 @@ export const AcquisitionPanel = ( return ( <> -
+
{!isEmpty(hubs?.data) ? (
) : ( -
-
-
- No AirDC++ hub configured. Please configure it in{" "} - Settings > AirDC++ > Hubs. -
-
-
+
+ No AirDC++ hub configured. Please configure it in{" "} + Settings > AirDC++ > Hubs. +
)}
+ {/* configured hub */} + {!isEmpty(hubs?.data) && ( + + + + + {hubs && hubs?.data[0].hub_url} + + )} {/* AirDC++ search instance details */} {!isNil(airDCPPSearchInstance) && @@ -274,7 +275,7 @@ export const AcquisitionPanel = (
- {hubs?.data.map((value, idx) => ( + {hubs?.data.map((value, idx: string) => ( {value.identity.name} @@ -313,7 +314,7 @@ export const AcquisitionPanel = ( )} {/* AirDC++ results */} -
+
{!isNil(airDCPPSearchResults) && !isEmpty(airDCPPSearchResults) ? (
@@ -334,118 +335,121 @@ export const AcquisitionPanel = ( - {map(airDCPPSearchResults, ({ result, search_id }, idx) => { - return ( - - + + - + - - - - ); - })} + + + + ); + }, + )}
-

- {result.type.id === "directory" ? ( - - ) : null} - {ellipsize(result.name, 70)} -

+ {map( + airDCPPSearchResults, + ({ dupe, type, name, id, slots, users, size }, idx) => { + return ( +
+

+ {type.id === "directory" ? ( + + ) : null} + {ellipsize(name, 70)} +

-
-
-
- {!isNil(result.dupe) ? ( +
+
+
+ {!isNil(dupe) ? ( + + + + + + + Dupe + + + ) : null} + + {/* Nicks */} - + - Dupe + {users.user.nicks} - ) : null} + {/* Flags */} + {users.user.flags.map((flag, idx) => ( + + + + - {/* Nicks */} - - - - - - - {result.users.user.nicks} - - - {/* Flags */} - {result.users.user.flags.map((flag, idx) => ( - - - + + {flag} + + ))} +
+
+
+
+ {/* Extension */} + + + + - - {flag} - - - ))} - - - - - {/* Extension */} - - - + + {type.str} + + + {/* Slots */} + + + + - - {result.type.str} + + {slots.total} slots; {slots.free} free + - - - {/* Slots */} - - - - - - - {result.slots.total} slots; {result.slots.free} free - - - - -
+ +
diff --git a/src/client/components/ComicDetail/AsyncSelectPaginate/AsyncSelectPaginate.tsx b/src/client/components/ComicDetail/AsyncSelectPaginate/AsyncSelectPaginate.tsx index a75ae99..2d8835e 100644 --- a/src/client/components/ComicDetail/AsyncSelectPaginate/AsyncSelectPaginate.tsx +++ b/src/client/components/ComicDetail/AsyncSelectPaginate/AsyncSelectPaginate.tsx @@ -1,11 +1,17 @@ import React, { ReactElement, useCallback, useState } from "react"; -import PropTypes from "prop-types"; import { fetchMetronResource } from "../../../actions/metron.actions"; import Creatable from "react-select/creatable"; import { withAsyncPaginate } from "react-select-async-paginate"; const CreatableAsyncPaginate = withAsyncPaginate(Creatable); -export const AsyncSelectPaginate = (props): ReactElement => { +interface AsyncSelectPaginateProps { + metronResource: string; + placeholder?: string; + value?: object; + onChange?(...args: unknown[]): unknown; +} + +export const AsyncSelectPaginate = (props: AsyncSelectPaginateProps): ReactElement => { const [value, setValue] = useState(null); const [isAddingInProgress, setIsAddingInProgress] = useState(false); @@ -38,11 +44,4 @@ export const AsyncSelectPaginate = (props): ReactElement => { ); }; -AsyncSelectPaginate.propTypes = { - metronResource: PropTypes.string.isRequired, - placeholder: PropTypes.string, - value: PropTypes.object, - onChange: PropTypes.func, -}; - export default AsyncSelectPaginate; diff --git a/src/client/components/ComicDetail/ComicVineDetails.tsx b/src/client/components/ComicDetail/ComicVineDetails.tsx index 8ef6cd1..f4a48fa 100644 --- a/src/client/components/ComicDetail/ComicVineDetails.tsx +++ b/src/client/components/ComicDetail/ComicVineDetails.tsx @@ -1,12 +1,21 @@ import React, { ReactElement } from "react"; -import PropTypes from "prop-types"; import { detectIssueTypes } from "../../shared/utils/tradepaperback.utils"; import dayjs from "dayjs"; import { isEmpty, isUndefined } from "lodash"; import Card from "../shared/Carda"; import { convert } from "html-to-text"; -export const ComicVineDetails = (props): ReactElement => { +interface ComicVineDetailsProps { + updatedAt?: string; + data?: { + name?: string; + number?: string; + resource_type?: string; + id?: number; + }; +} + +export const ComicVineDetails = (props: ComicVineDetailsProps): ReactElement => { const { data, updatedAt } = props; return (
@@ -107,13 +116,3 @@ export const ComicVineDetails = (props): ReactElement => { }; export default ComicVineDetails; - -ComicVineDetails.propTypes = { - updatedAt: PropTypes.string, - data: PropTypes.shape({ - name: PropTypes.string, - number: PropTypes.string, - resource_type: PropTypes.string, - id: PropTypes.number, - }), -}; diff --git a/src/client/components/ComicDetail/DownloadsPanel.tsx b/src/client/components/ComicDetail/DownloadsPanel.tsx index 2a4a9d8..2998d02 100644 --- a/src/client/components/ComicDetail/DownloadsPanel.tsx +++ b/src/client/components/ComicDetail/DownloadsPanel.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useContext, ReactElement, useState } from "react"; import { RootState } from "threetwo-ui-typings"; -import { isEmpty, map } from "lodash"; +import { isEmpty, isNil, isUndefined, map } from "lodash"; import { AirDCPPBundles } from "./AirDCPPBundles"; import { TorrentDownloads } from "./TorrentDownloads"; import { useQuery } from "@tanstack/react-query"; @@ -9,6 +9,7 @@ import { LIBRARY_SERVICE_BASE_URI, QBITTORRENT_SERVICE_BASE_URI, TORRENT_JOB_SERVICE_BASE_URI, + SOCKET_BASE_URI, } from "../../constants/endpoints"; import { useStore } from "../../store"; import { useShallow } from "zustand/react/shallow"; @@ -22,13 +23,11 @@ export const DownloadsPanel = ( props: IDownloadsPanelProps, ): ReactElement | null => { const { comicObjectId } = useParams<{ comicObjectId: string }>(); - const [bundles, setBundles] = useState([]); const [infoHashes, setInfoHashes] = useState([]); const [torrentDetails, setTorrentDetails] = useState([]); - const [activeTab, setActiveTab] = useState("torrents"); - const { airDCPPSocketInstance, socketIOInstance } = useStore( + const [activeTab, setActiveTab] = useState("directconnect"); + const { socketIOInstance } = useStore( useShallow((state: any) => ({ - airDCPPSocketInstance: state.airDCPPSocketInstance, socketIOInstance: state.socketIOInstance, })), ); @@ -44,32 +43,29 @@ export const DownloadsPanel = ( .filter((item) => item !== undefined); setTorrentDetails(torrents); }); - // Fetch the downloaded files and currently-downloading file(s) from AirDC++ - const { data: comicObject, isSuccess } = useQuery({ + + /** + * Query to fetch AirDC++ download bundles for a given comic resource Id + * @param {string} {comicObjectId} - A mongo id that identifies a comic document + */ + const { data: bundles } = useQuery({ queryKey: ["bundles"], queryFn: async () => await axios({ - url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, + url: `${LIBRARY_SERVICE_BASE_URI}/getBundles`, method: "POST", - headers: { - "Content-Type": "application/json; charset=utf-8", - }, data: { - id: `${comicObjectId}`, + comicObjectId, + config: { + protocol: `ws`, + hostname: `192.168.1.119:5600`, + username: `admin`, + password: `password`, + }, }, }), + enabled: activeTab !== "" && activeTab === "directconnect", }); - const getBundles = async (comicObject) => { - if (comicObject?.data.acquisition.directconnect) { - const filteredBundles = - comicObject.data.acquisition.directconnect.downloads.map( - async ({ bundleId }) => { - return await airDCPPSocketInstance.get(`queue/bundles/${bundleId}`); - }, - ); - return await Promise.all(filteredBundles); - } - }; // Call the scheduled job for fetching torrent data // triggered by the active tab been set to "torrents" @@ -83,14 +79,9 @@ export const DownloadsPanel = ( }, }), queryKey: [activeTab], + enabled: activeTab !== "" && activeTab === "torrents", }); - - useEffect(() => { - getBundles(comicObject).then((result) => { - setBundles(result); - }); - }, [comicObject]); - + console.log(bundles); return (
@@ -135,10 +126,14 @@ export const DownloadsPanel = (
- {activeTab === "torrents" && } - {!isEmpty(airDCPPSocketInstance) && - !isEmpty(bundles) && - activeTab === "directconnect" && } + {activeTab === "torrents" ? ( + + ) : null} + {!isNil(bundles?.data) && bundles?.data.length !== 0 ? ( + + ) : ( + "nutin" + )}
); }; diff --git a/src/client/components/ComicDetail/RawFileDetails.tsx b/src/client/components/ComicDetail/RawFileDetails.tsx index 3811038..44acf6e 100644 --- a/src/client/components/ComicDetail/RawFileDetails.tsx +++ b/src/client/components/ComicDetail/RawFileDetails.tsx @@ -1,10 +1,36 @@ import React, { ReactElement } from "react"; -import PropTypes from "prop-types"; import prettyBytes from "pretty-bytes"; import { isEmpty } from "lodash"; import { format, parseISO } from "date-fns"; -export const RawFileDetails = (props): ReactElement => { +interface RawFileDetailsProps { + data?: { + rawFileDetails?: { + containedIn?: string; + name?: string; + fileSize?: number; + path?: string; + extension?: string; + mimeType?: string; + cover?: { + filePath?: string; + }; + }; + inferredMetadata?: { + issue?: { + year?: string; + name?: string; + number?: number; + subtitle?: string; + }; + }; + created_at?: string; + updated_at?: string; + }; + children?: any; +} + +export const RawFileDetails = (props: RawFileDetailsProps): ReactElement => { const { rawFileDetails, inferredMetadata, created_at, updated_at } = props.data; return ( @@ -98,30 +124,3 @@ export const RawFileDetails = (props): ReactElement => { }; export default RawFileDetails; - -RawFileDetails.propTypes = { - data: PropTypes.shape({ - rawFileDetails: PropTypes.shape({ - containedIn: PropTypes.string, - name: PropTypes.string, - fileSize: PropTypes.number, - path: PropTypes.string, - extension: PropTypes.string, - mimeType: PropTypes.string, - cover: PropTypes.shape({ - filePath: PropTypes.string, - }), - }), - inferredMetadata: PropTypes.shape({ - issue: PropTypes.shape({ - year: PropTypes.string, - name: PropTypes.string, - number: PropTypes.number, - subtitle: PropTypes.string, - }), - }), - created_at: PropTypes.string, - updated_at: PropTypes.string, - }), - children: PropTypes.any, -}; diff --git a/src/client/components/ComicDetail/TorrentSearchPanel.tsx b/src/client/components/ComicDetail/TorrentSearchPanel.tsx index 8e8a035..67e9642 100644 --- a/src/client/components/ComicDetail/TorrentSearchPanel.tsx +++ b/src/client/components/ComicDetail/TorrentSearchPanel.tsx @@ -25,7 +25,7 @@ export const TorrentSearchPanel = (props) => { data: { prowlarrQuery: { port: "9696", - apiKey: "c4f42e265fb044dc81f7e88bd41c3367", + apiKey: "38c2656e8f5d4790962037b8c4416a8f", offset: 0, categories: [7030], query: searchTerm.issueName, diff --git a/src/client/components/Dashboard/PullList.tsx b/src/client/components/Dashboard/PullList.tsx index 8ea16f7..8e475c7 100644 --- a/src/client/components/Dashboard/PullList.tsx +++ b/src/client/components/Dashboard/PullList.tsx @@ -86,8 +86,8 @@ export const PullList = (): ReactElement => { Pull List aggregated for the week from{" "} - - League Of Comic Geeks + + Things From Another World @@ -132,13 +132,13 @@ export const PullList = (): ReactElement => {
- {issue.publisher} + {issue.publicationDate}