🔧 AirDCPP metadata and download first draft

This commit is contained in:
2021-08-23 14:34:55 -07:00
parent e5870fa1a8
commit 8a1e6fb98a
5 changed files with 162 additions and 84 deletions

View File

@@ -6,10 +6,9 @@ import {
SearchResponse, SearchResponse,
} from "threetwo-ui-typings"; } from "threetwo-ui-typings";
import { import {
AIRDCPP_SEARCH_INSTANCE_CREATED,
AIRDCPP_SEARCH_RESULTS_RECEIVED, AIRDCPP_SEARCH_RESULTS_RECEIVED,
AIRDCPP_HUB_SEARCHES_SENT, AIRDCPP_HUB_SEARCHES_SENT,
AIRDCPP_HUB_USER_CONNECTED, AIRDCPP_RESULT_DOWNLOAD_INITIATED,
} from "../constants/action-types"; } from "../constants/action-types";
interface SearchData { interface SearchData {
@@ -30,10 +29,10 @@ export const search = (data: SearchData) => async (dispatch) => {
`search/${instance.id}`, `search/${instance.id}`,
"search_hub_searches_sent", "search_hub_searches_sent",
async (searchInfo) => { async (searchInfo) => {
console.log("As", searchInfo)
// Collect the results for 5 seconds before fetching them
dispatch({ dispatch({
type: AIRDCPP_HUB_SEARCHES_SENT, type: AIRDCPP_HUB_SEARCHES_SENT,
searchInfo,
instance,
}); });
}, },
); );
@@ -52,3 +51,17 @@ export const search = (data: SearchData) => async (dispatch) => {
SocketService.disconnect(); SocketService.disconnect();
return results; return results;
}; };
export const downloadAirDCPPItem =
(instanceId: string, resultId: 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();
};

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback, ReactElement } from "react"; import React, { useState, useEffect, useCallback, ReactElement } from "react";
import { search } from "../actions/airdcpp.actions"; import { search, downloadAirDCPPItem } from "../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings"; import { RootState, SearchInstance } from "threetwo-ui-typings";
import { each, isNil, map } from "lodash"; import { each, isNil, map } from "lodash";
interface IAcquisitionPanelProps { interface IAcquisitionPanelProps {
@@ -17,8 +17,14 @@ export const AcquisitionPanel = (
const airDCPPSearchResults = useSelector( const airDCPPSearchResults = useSelector(
(state: RootState) => state.airdcpp.results, (state: RootState) => state.airdcpp.results,
); );
const searchStatus = useSelector( const isAirDCPPSearchInProgress = useSelector(
(state: RootState) => state.airdcpp.searchStatus, (state: RootState) => state.airdcpp.isAirDCPPSearchInProgress,
);
const searchInfo = useSelector(
(state: RootState) => state.airdcpp.searchInfo,
);
const searchInstance: SearchInstance = useSelector(
(state: RootState) => state.airdcpp.searchInstance,
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const getDCPPSearchResults = useCallback( const getDCPPSearchResults = useCallback(
@@ -30,75 +36,133 @@ export const AcquisitionPanel = (
const dcppQuery = { const dcppQuery = {
query: { query: {
pattern: `${volumeName}`, pattern: `${volumeName}`,
// file_type: "compressed",
extensions: ["cbz", "cbr"], extensions: ["cbz", "cbr"],
}, },
hub_urls: ["perfection.crabdance.com:777"], hub_urls: ["perfection.crabdance.com:777"],
priority: 1, priority: 1,
}; };
const downloadDCPPResult = useCallback(
(searchInstanceId, resultId) =>
dispatch(downloadAirDCPPItem(searchInstanceId, resultId)),
[dispatch],
);
return ( return (
<div className="comic-detail"> <>
<button <div className="comic-detail columns">
className="button" <div className="column is-one-fifth">
onClick={() => getDCPPSearchResults(dcppQuery)} <button
> className={
Search on AirDC++ isAirDCPPSearchInProgress
</button> ? "button is-loading is-warning"
<p>{searchStatus}</p> : "button"
{/* results */} }
{!isNil(airDCPPSearchResults) && ( onClick={() => getDCPPSearchResults(dcppQuery)}
<table className="table is-striped"> >
<thead> Search on AirDC++
<tr> </button>
<th>Name</th> </div>
<th>Type</th> {!isNil(searchInfo) && !isNil(searchInstance) && (
<th>Slots</th> <>
</tr> <div className="column is-one-quarter is-size-7">
</thead> <div className="card">
<tbody> <div className="card-content">
{map(airDCPPSearchResults, ({ name, type, slots, users }) => { <div className="content">
return (
<tr>
<td>
<p className="mb-2">
{type.id === "directory" ? (
<i className="fas fa-folder"></i>
) : null}{" "}
{name}
</p>
<dl> <dl>
<dt>Query: {searchInfo.query.pattern}</dt>
<dd> <dd>
<div className="tags"> Extensions: {searchInfo.query.extensions.join(", ")}
<span className="tag is-light is-info">
{users.user.nicks}
</span>
{users.user.flags.map((flag, idx) => (
<span className="tag is-light" key={idx}>
{flag}
</span>
))}
</div>
</dd> </dd>
<dd>File type: {searchInfo.query.file_type}</dd>
</dl> </dl>
</td> </div>
<td> </div>
<span className="tag is-light is-info"> </div>
{type.id === "directory" ? "directory" : type.str} </div>
</span> <div className="column is-one-quarter is-size-7">
</td> <div className="card">
<td> <div className="card-content">
<div className="tags has-addons"> <div className="content">
<span className="tag is-success">{slots.free} free</span> <dl>
<span className="tag is-light">{slots.total}</span> <dt>Search Instance: {searchInstance.id}</dt>
</div> <dt>Owned by {searchInstance.owner}</dt>
</td> <dd>Expires in: {searchInstance.expires_in}</dd>
</tr> </dl>
); </div>
})} </div>
</tbody> </div>
</table> </div>
)} </>
</div> )}
</div>
{/* results */}
<div>
{!isNil(airDCPPSearchResults) && (
<table className="table is-striped">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Slots</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{map(airDCPPSearchResults, ({ name, type, slots, users, id }) => {
return (
<tr>
<td>
<p className="mb-2">
{type.id === "directory" ? (
<i className="fas fa-folder"></i>
) : null}{" "}
{name}
</p>
<dl>
<dd>
<div className="tags">
<span className="tag is-light is-info">
{users.user.nicks}
</span>
{users.user.flags.map((flag, idx) => (
<span className="tag is-light" key={idx}>
{flag}
</span>
))}
</div>
</dd>
</dl>
</td>
<td>
<span className="tag is-light is-info">
{type.id === "directory" ? "directory" : type.str}
</span>
</td>
<td>
<div className="tags has-addons">
<span className="tag is-success">
{slots.free} free
</span>
<span className="tag is-light">{slots.total}</span>
</div>
</td>
<td>
<a
onClick={() =>
downloadDCPPResult(searchInstance.id, id)
}
>
<i className="fas fa-file-download"></i>
</a>
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</>
); );
}; };

View File

@@ -151,6 +151,12 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
name: "Acquisition", name: "Acquisition",
content: <AcquisitionPanel comicBookMetadata={comicBookDetailData} />, content: <AcquisitionPanel comicBookMetadata={comicBookDetailData} />,
}, },
{
id: 4,
icon: <i className="fas fa-cloud-download-alt"></i>,
name: "Downloads",
content: <div>Downloads</div>,
},
]; ];
const MetadataTabGroup = () => { const MetadataTabGroup = () => {
return ( return (

View File

@@ -31,11 +31,9 @@ export const IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS =
export const IMS_COMIC_BOOK_DB_OBJECT_CALL_FAILED = export const IMS_COMIC_BOOK_DB_OBJECT_CALL_FAILED =
"IMS_COMIC_BOOK_DB_OBJECT_CALL_FAILED"; "IMS_COMIC_BOOK_DB_OBJECT_CALL_FAILED";
export const AIRDCPP_SEARCH_INSTANCE_CREATED =
"AIRDCPP_SEARCH_INSTANCE_CREATED";
export const AIRDCPP_SEARCH_IN_PROGRESS = "AIRDCPP_SEARCH_IN_PROGRESS"; export const AIRDCPP_SEARCH_IN_PROGRESS = "AIRDCPP_SEARCH_IN_PROGRESS";
export const AIRDCPP_SEARCH_RESULTS_RECEIVED = export const AIRDCPP_SEARCH_RESULTS_RECEIVED =
"AIRDCPP_SEARCH_RESULTS_RECEIVED"; "AIRDCPP_SEARCH_RESULTS_RECEIVED";
export const AIRDCPP_HUB_SEARCHES_SENT = "AIRDCPP_HUB_SEARCHES_SENT"; export const AIRDCPP_HUB_SEARCHES_SENT = "AIRDCPP_HUB_SEARCHES_SENT";
export const AIRDCPP_HUB_USER_CONNECTED = "AIRDCPP_HUB_USER_CONNECTED"; export const AIRDCPP_RESULT_DOWNLOAD_INITIATED = "AIRDCPP_RESULT_DOWNLOAD_INITIATED";

View File

@@ -1,30 +1,19 @@
import { import {
AIRDCPP_SEARCH_INSTANCE_CREATED,
AIRDCPP_SEARCH_IN_PROGRESS, AIRDCPP_SEARCH_IN_PROGRESS,
AIRDCPP_SEARCH_RESULTS_RECEIVED, AIRDCPP_SEARCH_RESULTS_RECEIVED,
AIRDCPP_HUB_SEARCHES_SENT, AIRDCPP_HUB_SEARCHES_SENT,
AIRDCPP_HUB_USER_CONNECTED, AIRDCPP_RESULT_DOWNLOAD_INITIATED,
} from "../constants/action-types"; } from "../constants/action-types";
const initialState = { const initialState = {
isAirDCPPSearchInProgress: false, isAirDCPPSearchInProgress: false,
searchStatus: "", searchStatus: "",
searchInfo: null,
searchInstance: null,
}; };
function airdcppReducer(state = initialState, action) { function airdcppReducer(state = initialState, action) {
switch (action.type) { switch (action.type) {
case AIRDCPP_HUB_USER_CONNECTED:
return {
...state,
searchStatus: "Hub user connected",
}
case AIRDCPP_SEARCH_INSTANCE_CREATED:
return {
...state,
searchInstance: action.searchInstance,
searchStatus: "Search Instance created",
};
case AIRDCPP_SEARCH_IN_PROGRESS: case AIRDCPP_SEARCH_IN_PROGRESS:
return { return {
...state, ...state,
@@ -35,6 +24,7 @@ function airdcppReducer(state = initialState, action) {
return { return {
...state, ...state,
isAirDCPPSearchInProgress: false, isAirDCPPSearchInProgress: false,
searchStatus: "Search complete",
results: action.results, results: action.results,
}; };
case AIRDCPP_HUB_SEARCHES_SENT: case AIRDCPP_HUB_SEARCHES_SENT:
@@ -42,6 +32,13 @@ function airdcppReducer(state = initialState, action) {
...state, ...state,
searchStatus: "Hub searches sent", searchStatus: "Hub searches sent",
isAirDCPPSearchInProgress: true, isAirDCPPSearchInProgress: true,
searchInfo: action.searchInfo,
searchInstance: action.instance,
};
case AIRDCPP_RESULT_DOWNLOAD_INITIATED:
return {
...state,
downloadResult: action.downloadResult,
}; };
default: default: