diff --git a/src/client/actions/airdcpp.actions.tsx b/src/client/actions/airdcpp.actions.tsx index 94a8998..f2c633c 100644 --- a/src/client/actions/airdcpp.actions.tsx +++ b/src/client/actions/airdcpp.actions.tsx @@ -9,7 +9,10 @@ import { AIRDCPP_SEARCH_RESULTS_RECEIVED, AIRDCPP_HUB_SEARCHES_SENT, AIRDCPP_RESULT_DOWNLOAD_INITIATED, + AIRDCPP_DOWNLOAD_PROGRESS_TICK, } from "../constants/action-types"; +import { isNil } from "lodash"; +import axios from "axios"; interface SearchData { query: Pick & Partial>; @@ -53,15 +56,58 @@ export const search = (data: SearchData) => async (dispatch) => { }; export const downloadAirDCPPItem = - (instanceId: string, resultId: string): void => + (instanceId: string, resultId: string, comicObjectId: string): void => async (dispatch) => { - await SocketService.connect("admin", "password", true); - const downloadResult = await SocketService.post( - `search/${instanceId}/results/${resultId}/download`, - ); - dispatch({ - type: AIRDCPP_RESULT_DOWNLOAD_INITIATED, - downloadResult: downloadResult, - }); - SocketService.disconnect(); + try { + let bundleDBImportResult = {}; + await SocketService.connect("admin", "password", true); + const downloadResult = await SocketService.post( + `search/${instanceId}/results/${resultId}/download`, + ); + + if (!isNil(downloadResult)) { + bundleDBImportResult = await axios({ + method: "POST", + url: "http://localhost:3000/api/import/applyAirDCPPDownloadMetadata", + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + data: { + resultId, + comicObjectId, + downloadResult, + searchInstanceId: instanceId, + }, + }); + dispatch({ + type: AIRDCPP_RESULT_DOWNLOAD_INITIATED, + downloadResult: downloadResult, + bundleDBImportResult, + }); + } + + SocketService.disconnect(); + } catch (error) { + throw error; + } + }; + +export const getDownloadProgress = + (fileId: string, directoryId?: string): void => + async (dispatch) => { + try { + await SocketService.connect("admin", "password", true); + SocketService.addListener( + `queue`, + "queue_bundle_tick", + async (downloadProgressData) => { + dispatch({ + type: AIRDCPP_DOWNLOAD_PROGRESS_TICK, + downloadProgressData, + }); + }, + ); + } catch (error) { + throw error; + } }; diff --git a/src/client/actions/fileops.actions.tsx b/src/client/actions/fileops.actions.tsx index 31040bc..625f77b 100644 --- a/src/client/actions/fileops.actions.tsx +++ b/src/client/actions/fileops.actions.tsx @@ -1,9 +1,5 @@ import axios from "axios"; -import { - IFolderData, - IExtractedComicBookCoverFile, - IComicVineSearchQuery, -} from "threetwo-ui-typings"; +import { IFolderData, IExtractedComicBookCoverFile } from "threetwo-ui-typings"; import { API_BASE_URI, SOCKET_BASE_URI } from "../constants/endpoints"; import { io } from "socket.io-client"; import { @@ -18,7 +14,6 @@ import { IMS_CV_METADATA_IMPORT_FAILED, } from "../constants/action-types"; import { refineQuery } from "../shared/utils/filenameparser.utils"; -import { extend } from "lodash"; import sortBy from "array-sort-by"; export async function walkFolder(path: string): Promise> { diff --git a/src/client/components/AcquisitionPanel.tsx b/src/client/components/AcquisitionPanel.tsx index 2c9163f..f04841f 100644 --- a/src/client/components/AcquisitionPanel.tsx +++ b/src/client/components/AcquisitionPanel.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useCallback, ReactElement } from "react"; +import React, { useCallback, ReactElement } from "react"; import { search, downloadAirDCPPItem } from "../actions/airdcpp.actions"; import { useDispatch, useSelector } from "react-redux"; import { RootState, SearchInstance } from "threetwo-ui-typings"; -import { each, isNil, map } from "lodash"; +import { isNil, map } from "lodash"; interface IAcquisitionPanelProps { comicBookMetadata: any; @@ -13,6 +13,7 @@ export const AcquisitionPanel = ( ): ReactElement => { const volumeName = props.comicBookMetadata.sourcedMetadata.comicvine.volumeInformation.name; + const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, ""); const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name; const airDCPPSearchResults = useSelector( (state: RootState) => state.airdcpp.results, @@ -26,6 +27,7 @@ export const AcquisitionPanel = ( const searchInstance: SearchInstance = useSelector( (state: RootState) => state.airdcpp.searchInstance, ); + const dispatch = useDispatch(); const getDCPPSearchResults = useCallback( (searchQuery) => { @@ -35,16 +37,18 @@ export const AcquisitionPanel = ( ); const dcppQuery = { query: { - pattern: `${volumeName}`, + pattern: `${sanitizedVolumeName}`, extensions: ["cbz", "cbr"], }, - hub_urls: ["perfection.crabdance.com:777"], + hub_urls: ["nmdcs://piter.feardc.net:411"], priority: 1, }; const downloadDCPPResult = useCallback( - (searchInstanceId, resultId) => - dispatch(downloadAirDCPPItem(searchInstanceId, resultId)), + (searchInstanceId, resultId, comicBookObjectId) => + dispatch( + downloadAirDCPPItem(searchInstanceId, resultId, comicBookObjectId), + ), [dispatch], ); return ( @@ -62,6 +66,7 @@ export const AcquisitionPanel = ( Search on AirDC++ + {/* AirDC++ search instance details */} {!isNil(searchInfo) && !isNil(searchInstance) && ( <>
@@ -95,7 +100,7 @@ export const AcquisitionPanel = ( )}
- {/* results */} + {/* AirDC++ results */}
{!isNil(airDCPPSearchResults) && ( @@ -110,7 +115,7 @@ export const AcquisitionPanel = ( {map(airDCPPSearchResults, ({ name, type, slots, users, id }) => { return ( - +

{type.id === "directory" ? ( @@ -149,7 +154,11 @@ export const AcquisitionPanel = (

- downloadDCPPResult(searchInstance.id, id) + downloadDCPPResult( + searchInstance.id, + id, + props.comicBookMetadata._id, + ) } > diff --git a/src/client/components/ComicDetail.tsx b/src/client/components/ComicDetail.tsx index 6d3cf24..2b9ad13 100644 --- a/src/client/components/ComicDetail.tsx +++ b/src/client/components/ComicDetail.tsx @@ -4,6 +4,7 @@ import Card from "./Carda"; import MatchResult from "./MatchResult"; import ComicVineSearchForm from "./ComicVineSearchForm"; import AcquisitionPanel from "./AcquisitionPanel"; +import DownloadsPanel from "./DownloadsPanel"; import { css } from "@emotion/react"; import PuffLoader from "react-spinners/PuffLoader"; @@ -50,6 +51,10 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => { const comicBookDetailData = useSelector( (state: RootState) => state.comicInfo.comicBookDetail, ); + + const bundleMetadata = useSelector( + (state: RootState) => state.comicInfo.downloadResult, + ); const { comicObjectId } = useParams<{ comicObjectId: string }>(); const dispatch = useDispatch(); const toggleActionDropdown = () => @@ -60,7 +65,6 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => { }, [page, dispatch]); const openDrawerWithCVMatches = useCallback(() => { - console.log("here") setVisible(true); dispatch(fetchComicVineMatches(comicBookDetailData)); }, [dispatch, comicBookDetailData]); @@ -155,7 +159,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => { id: 4, icon: , name: "Downloads", - content:
Downloads
, + content: , }, ]; const MetadataTabGroup = () => { diff --git a/src/client/components/DownloadsPanel.tsx b/src/client/components/DownloadsPanel.tsx new file mode 100644 index 0000000..ae516e2 --- /dev/null +++ b/src/client/components/DownloadsPanel.tsx @@ -0,0 +1,51 @@ +import React, { useCallback, useEffect, useState, ReactElement } from "react"; +import { getDownloadProgress } from "../actions/airdcpp.actions"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState, SearchInstance } from "threetwo-ui-typings"; +import { isNil, map } from "lodash"; +import prettyBytes from "pretty-bytes"; + +interface IDownloadsPanelProps { + data: any; +} + +export const DownloadsPanel = ( + props: IDownloadsPanelProps, +): ReactElement | null => { + const downloadProgressTick = useSelector( + (state: RootState) => state.airdcpp.downloadProgressData, + ); + const dispatch = useDispatch(); + useEffect(() => { + dispatch(getDownloadProgress("12312")); + }, [dispatch]); + return !isNil(downloadProgressTick) ? ( +
+ {JSON.stringify(downloadProgressTick)} + + {(parseInt(downloadProgressTick.downloaded_bytes) / + parseInt(downloadProgressTick.size)) * + 100} + % + +
+
{downloadProgressTick.name}
+
+ {prettyBytes(downloadProgressTick.downloaded_bytes)} of + {prettyBytes(downloadProgressTick.size)} +
+
+ {prettyBytes(downloadProgressTick.speed)} per second. Time left: + {parseInt(downloadProgressTick.seconds_left) / 60} +
+
{downloadProgressTick.target}
+
+
+ ) : null; +}; + +export default DownloadsPanel; diff --git a/src/client/constants/action-types.ts b/src/client/constants/action-types.ts index 9c7eff1..1d1213a 100644 --- a/src/client/constants/action-types.ts +++ b/src/client/constants/action-types.ts @@ -36,4 +36,6 @@ export const AIRDCPP_SEARCH_RESULTS_RECEIVED = "AIRDCPP_SEARCH_RESULTS_RECEIVED"; export const AIRDCPP_HUB_SEARCHES_SENT = "AIRDCPP_HUB_SEARCHES_SENT"; -export const AIRDCPP_RESULT_DOWNLOAD_INITIATED = "AIRDCPP_RESULT_DOWNLOAD_INITIATED"; +export const AIRDCPP_RESULT_DOWNLOAD_INITIATED = + "AIRDCPP_RESULT_DOWNLOAD_INITIATED"; +export const AIRDCPP_DOWNLOAD_PROGRESS_TICK = "AIRDCPP_DOWNLOAD_PROGRESS_TICK"; diff --git a/src/client/reducers/airdcpp.reducer.ts b/src/client/reducers/airdcpp.reducer.ts index f72b1c3..2edbc11 100644 --- a/src/client/reducers/airdcpp.reducer.ts +++ b/src/client/reducers/airdcpp.reducer.ts @@ -3,6 +3,7 @@ import { AIRDCPP_SEARCH_RESULTS_RECEIVED, AIRDCPP_HUB_SEARCHES_SENT, AIRDCPP_RESULT_DOWNLOAD_INITIATED, + AIRDCPP_DOWNLOAD_PROGRESS_TICK, } from "../constants/action-types"; const initialState = { @@ -10,6 +11,8 @@ const initialState = { searchStatus: "", searchInfo: null, searchInstance: null, + downloadResult: null, + bundleDBImportResult: null, }; function airdcppReducer(state = initialState, action) { @@ -39,6 +42,12 @@ function airdcppReducer(state = initialState, action) { return { ...state, downloadResult: action.downloadResult, + bundleDBImportResult: action.bundleDBImportResult, + }; + case AIRDCPP_DOWNLOAD_PROGRESS_TICK: + return { + ...state, + downloadProgressData: action.downloadProgressData, }; default: