🔧 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,
} from "threetwo-ui-typings";
import {
AIRDCPP_SEARCH_INSTANCE_CREATED,
AIRDCPP_SEARCH_RESULTS_RECEIVED,
AIRDCPP_HUB_SEARCHES_SENT,
AIRDCPP_HUB_USER_CONNECTED,
AIRDCPP_RESULT_DOWNLOAD_INITIATED,
} from "../constants/action-types";
interface SearchData {
@@ -30,10 +29,10 @@ export const search = (data: SearchData) => async (dispatch) => {
`search/${instance.id}`,
"search_hub_searches_sent",
async (searchInfo) => {
console.log("As", searchInfo)
// Collect the results for 5 seconds before fetching them
dispatch({
type: AIRDCPP_HUB_SEARCHES_SENT,
searchInfo,
instance,
});
},
);
@@ -52,3 +51,17 @@ export const search = (data: SearchData) => async (dispatch) => {
SocketService.disconnect();
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 { search } from "../actions/airdcpp.actions";
import { search, downloadAirDCPPItem } from "../actions/airdcpp.actions";
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";
interface IAcquisitionPanelProps {
@@ -17,8 +17,14 @@ export const AcquisitionPanel = (
const airDCPPSearchResults = useSelector(
(state: RootState) => state.airdcpp.results,
);
const searchStatus = useSelector(
(state: RootState) => state.airdcpp.searchStatus,
const isAirDCPPSearchInProgress = useSelector(
(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 getDCPPSearchResults = useCallback(
@@ -30,75 +36,133 @@ export const AcquisitionPanel = (
const dcppQuery = {
query: {
pattern: `${volumeName}`,
// file_type: "compressed",
extensions: ["cbz", "cbr"],
},
hub_urls: ["perfection.crabdance.com:777"],
priority: 1,
};
const downloadDCPPResult = useCallback(
(searchInstanceId, resultId) =>
dispatch(downloadAirDCPPItem(searchInstanceId, resultId)),
[dispatch],
);
return (
<div className="comic-detail">
<button
className="button"
onClick={() => getDCPPSearchResults(dcppQuery)}
>
Search on AirDC++
</button>
<p>{searchStatus}</p>
{/* results */}
{!isNil(airDCPPSearchResults) && (
<table className="table is-striped">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Slots</th>
</tr>
</thead>
<tbody>
{map(airDCPPSearchResults, ({ name, type, slots, users }) => {
return (
<tr>
<td>
<p className="mb-2">
{type.id === "directory" ? (
<i className="fas fa-folder"></i>
) : null}{" "}
{name}
</p>
<>
<div className="comic-detail columns">
<div className="column is-one-fifth">
<button
className={
isAirDCPPSearchInProgress
? "button is-loading is-warning"
: "button"
}
onClick={() => getDCPPSearchResults(dcppQuery)}
>
Search on AirDC++
</button>
</div>
{!isNil(searchInfo) && !isNil(searchInstance) && (
<>
<div className="column is-one-quarter is-size-7">
<div className="card">
<div className="card-content">
<div className="content">
<dl>
<dt>Query: {searchInfo.query.pattern}</dt>
<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>
Extensions: {searchInfo.query.extensions.join(", ")}
</dd>
<dd>File type: {searchInfo.query.file_type}</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>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</div>
</div>
</div>
</div>
<div className="column is-one-quarter is-size-7">
<div className="card">
<div className="card-content">
<div className="content">
<dl>
<dt>Search Instance: {searchInstance.id}</dt>
<dt>Owned by {searchInstance.owner}</dt>
<dd>Expires in: {searchInstance.expires_in}</dd>
</dl>
</div>
</div>
</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",
content: <AcquisitionPanel comicBookMetadata={comicBookDetailData} />,
},
{
id: 4,
icon: <i className="fas fa-cloud-download-alt"></i>,
name: "Downloads",
content: <div>Downloads</div>,
},
];
const MetadataTabGroup = () => {
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 =
"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_RESULTS_RECEIVED =
"AIRDCPP_SEARCH_RESULTS_RECEIVED";
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 {
AIRDCPP_SEARCH_INSTANCE_CREATED,
AIRDCPP_SEARCH_IN_PROGRESS,
AIRDCPP_SEARCH_RESULTS_RECEIVED,
AIRDCPP_HUB_SEARCHES_SENT,
AIRDCPP_HUB_USER_CONNECTED,
AIRDCPP_RESULT_DOWNLOAD_INITIATED,
} from "../constants/action-types";
const initialState = {
isAirDCPPSearchInProgress: false,
searchStatus: "",
searchInfo: null,
searchInstance: null,
};
function airdcppReducer(state = initialState, action) {
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:
return {
...state,
@@ -35,6 +24,7 @@ function airdcppReducer(state = initialState, action) {
return {
...state,
isAirDCPPSearchInProgress: false,
searchStatus: "Search complete",
results: action.results,
};
case AIRDCPP_HUB_SEARCHES_SENT:
@@ -42,6 +32,13 @@ function airdcppReducer(state = initialState, action) {
...state,
searchStatus: "Hub searches sent",
isAirDCPPSearchInProgress: true,
searchInfo: action.searchInfo,
searchInstance: action.instance,
};
case AIRDCPP_RESULT_DOWNLOAD_INITIATED:
return {
...state,
downloadResult: action.downloadResult,
};
default: