🐂 Queue controls

This commit is contained in:
2023-08-22 22:07:33 -05:00
parent b4ef0b7817
commit debd9a20bf
7 changed files with 183 additions and 111 deletions

View File

@@ -35,6 +35,7 @@ import {
VOLUMES_FETCHED, VOLUMES_FETCHED,
CV_WEEKLY_PULLLIST_FETCHED, CV_WEEKLY_PULLLIST_FETCHED,
LIBRARY_SERVICE_HEALTH, LIBRARY_SERVICE_HEALTH,
LS_SET_QUEUE_STATUS,
} from "../constants/action-types"; } from "../constants/action-types";
import { success } from "react-notification-system-redux"; import { success } from "react-notification-system-redux";
@@ -96,13 +97,14 @@ export const fetchComicBookMetadata = () => async (dispatch) => {
data: {}, data: {},
}); });
}; };
export const toggleImportQueueStatus = (options) => async (dispatch) => { export const setQueueControl =
dispatch({ (queueAction: string, queueStatus: string) => async (dispatch) => {
type: LS_TOGGLE_IMPORT_QUEUE, dispatch({
meta: { remote: true }, type: LS_SET_QUEUE_STATUS,
data: { manjhul: "jigyadam", action: options.action }, meta: { remote: true },
}); data: { queueAction, queueStatus },
}; });
};
/** /**
* Fetches comic book metadata for various types * Fetches comic book metadata for various types
* @return metadata for the comic book object categories * @return metadata for the comic book object categories

View File

@@ -108,9 +108,6 @@ export const App = (): ReactElement => {
meta: { remote: true }, meta: { remote: true },
session: { sessionId }, session: { sessionId },
}); });
socketIOConnectionInstance.on("yelaveda", (data) => {
console.log(data);
});
} else { } else {
// Inititalize the session and persist the sessionId to localStorage // Inititalize the session and persist the sessionId to localStorage
socketIOConnectionInstance.on("sessionInitialized", (sessionId) => { socketIOConnectionInstance.on("sessionInitialized", (sessionId) => {

View File

@@ -1,4 +1,10 @@
import React, { ReactElement, useCallback, useContext, useEffect, useState } from "react"; import React, {
ReactElement,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { getTransfers } from "../../actions/airdcpp.actions"; import { getTransfers } from "../../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket"; import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
@@ -20,7 +26,9 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
const airDCPPTransfers = useSelector( const airDCPPTransfers = useSelector(
(state: RootState) => state.airdcpp.transfers, (state: RootState) => state.airdcpp.transfers,
); );
const issueBundles = useSelector((state: RootState) => state.airdcpp.issue_bundles); const issueBundles = useSelector(
(state: RootState) => state.airdcpp.issue_bundles,
);
const [bundles, setBundles] = useState([]); const [bundles, setBundles] = useState([]);
// Make the call to get all transfers from AirDC++ // Make the call to get all transfers from AirDC++
useEffect(() => { useEffect(() => {
@@ -37,18 +45,26 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
useEffect(() => { useEffect(() => {
if (!isUndefined(issueBundles)) { if (!isUndefined(issueBundles)) {
const foo = issueBundles.data.map((bundle) => { const foo = issueBundles.data.map((bundle) => {
const { rawFileDetails, inferredMetadata, acquisition: { directconnect: { downloads } }, sourcedMetadata: { locg, comicvine } } = bundle; const {
rawFileDetails,
inferredMetadata,
acquisition: {
directconnect: { downloads },
},
sourcedMetadata: { locg, comicvine },
} = bundle;
const { issueName, url } = determineCoverFile({ const { issueName, url } = determineCoverFile({
rawFileDetails, comicvine, locg, rawFileDetails,
comicvine,
locg,
}); });
return { ...bundle, issueName, url } return { ...bundle, issueName, url };
});
})
setBundles(foo); setBundles(foo);
} }
}, [issueBundles]) }, [issueBundles]);
return !isNil(bundles) ? return !isNil(bundles) ? (
<div className="container"> <div className="container">
<section className="section"> <section className="section">
<h1 className="title">Downloads</h1> <h1 className="title">Downloads</h1>
@@ -56,45 +72,59 @@ export const Downloads = (props: IDownloadsProps): ReactElement => {
<div className="column is-half"> <div className="column is-half">
{bundles.map((bundle, idx) => { {bundles.map((bundle, idx) => {
console.log(bundle); console.log(bundle);
return <div key={idx}> return (
<MetadataPanel <div key={idx}>
data={bundle} <MetadataPanel
imageStyle={{ maxWidth: 80 }} data={bundle}
titleStyle={{ fontSize: "0.8rem" }} imageStyle={{ maxWidth: 80 }}
tagsStyle={{ fontSize: "0.7rem" }} titleStyle={{ fontSize: "0.8rem" }}
containerStyle={{ tagsStyle={{ fontSize: "0.7rem" }}
maxWidth: 400, containerStyle={{
padding: 0, maxWidth: 400,
margin: "0 0 8px 0", padding: 0,
}} /> margin: "0 0 8px 0",
}}
/>
<table className="table is-size-7"> <table className="table is-size-7">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Size</th> <th>Size</th>
<th>Type</th> <th>Type</th>
<th>Bundle ID</th> <th>Bundle ID</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{bundle.acquisition.directconnect.downloads.map((bundle, idx) => { {bundle.acquisition.directconnect.downloads.map(
return (<tr key={idx}> (bundle, idx) => {
<td>{bundle.name}</td> return (
<td>{bundle.size}</td> <tr key={idx}>
<td>{bundle.type.str}</td> <td>{bundle.name}</td>
<td><span className="tag is-warning">{bundle.bundleId}</span></td> <td>{bundle.size}</td>
</tr>) <td>{bundle.type.str}</td>
})} <td>
</tbody> <span className="tag is-warning">
</table> {bundle.bundleId}
{/* <pre>{JSON.stringify(bundle.acquisition.directconnect.downloads, null, 2)}</pre> */} </span>
</div> </td>
</tr>
);
},
)}
</tbody>
</table>
{/* <pre>{JSON.stringify(bundle.acquisition.directconnect.downloads, null, 2)}</pre> */}
</div>
);
})} })}
</div> </div>
</div> </div>
</section> </section>
</div> : <div>There are no downloads.</div>; </div>
) : (
<div>There are no downloads.</div>
);
}; };
export default Downloads; export default Downloads;

View File

@@ -1,22 +1,20 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
type IHeaderProps = { type IHeaderProps = {
headerContent: string; headerContent: string;
subHeaderContent: string; subHeaderContent: string;
iconClassNames: string; iconClassNames: string;
} };
export const Header = (props: IHeaderProps): ReactElement => { export const Header = (props: IHeaderProps): ReactElement => {
return (
<>
<h4 className="title is-4">
<i className={props.iconClassNames}></i> {props.headerContent}
</h4>
<p className="subtitle is-7">{props.subHeaderContent}</p>
</>
);
};
return (<> export default Header;
<h4 className="title is-4">
<i className={props.iconClassNames}></i> {props.headerContent}
</h4>
<p className="subtitle is-7">
{props.subHeaderContent}
</p>
</>)
}
export default Header;

View File

@@ -2,11 +2,15 @@ import React, { ReactElement, useCallback, useContext, useState } from "react";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { import {
fetchComicBookMetadata, fetchComicBookMetadata,
toggleImportQueueStatus, setQueueControl,
} from "../actions/fileops.actions"; } from "../actions/fileops.actions";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css"; import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Loader from "react-loader-spinner"; import Loader from "react-loader-spinner";
import { isUndefined } from "lodash"; import { isUndefined } from "lodash";
import {
LS_IMPORT_CALL_IN_PROGRESS,
LS_SET_QUEUE_STATUS,
} from "../constants/action-types";
interface IProps { interface IProps {
matches?: unknown; matches?: unknown;
@@ -34,40 +38,62 @@ export const Import = (props: IProps): ReactElement => {
(state: RootState) => state.fileOps.librarySearchResultCount, (state: RootState) => state.fileOps.librarySearchResultCount,
); );
const failedImportJobCount = useSelector( const failedImportJobCount = useSelector(
(state: RootState) => state.fileOps.failedImportJobCount, (state: RootState) => state.fileOps.failedJobCount,
); );
const lastQueueJob = useSelector( const lastQueueJob = useSelector(
(state: RootState) => state.fileOps.lastQueueJob, (state: RootState) => state.fileOps.lastQueueJob,
); );
const libraryQueueImportStatus = useSelector( const libraryQueueImportStatus = useSelector(
(state: RootState) => state.fileOps.IMSCallInProgress, (state: RootState) => state.fileOps.LSQueueImportStatus,
); );
const [isImportQueuePaused, setImportQueueStatus] = useState(undefined);
const initiateImport = useCallback(() => { const initiateImport = useCallback(() => {
if (typeof props.path !== "undefined") { if (typeof props.path !== "undefined") {
dispatch(fetchComicBookMetadata(props.path)); dispatch(fetchComicBookMetadata(props.path));
} }
}, [dispatch]); }, [dispatch]);
const toggleImport = useCallback(() => { const toggleQueue = useCallback(
setImportQueueStatus(!isImportQueuePaused); (queueAction: string, queueStatus: string) => {
if (isImportQueuePaused === true) { dispatch(setQueueControl(queueAction, queueStatus));
dispatch(toggleImportQueueStatus({ action: "resume" })); },
} else if (isImportQueuePaused === false) { [],
dispatch(toggleImportQueueStatus({ action: "pause" })); );
const renderQueueControls = (status: string): ReactElement | null => {
switch (status) {
case "running":
return (
<div className="control">
<button
className="button is-warning is-light"
onClick={() => toggleQueue("pause", "paused")}
>
<i className="fa-solid fa-pause mr-2"></i> Pause
</button>
</div>
);
case "paused":
return (
<div className="control">
<button
className="button is-success is-light"
onClick={() => toggleQueue("resume", "running")}
>
<i className="fa-solid fa-play mr-2"></i> Resume
</button>
</div>
);
case "drained":
return null;
default:
return null;
} }
}, [isImportQueuePaused]); };
const pauseIconText = (
<>
<i className="fa-solid fa-pause mr-2"></i> Pause
</>
);
const playIconText = (
<>
<i className="fa-solid fa-play mr-2"></i> Resume
</>
);
return ( return (
<div className="container"> <div className="container">
<section className="section is-small"> <section className="section is-small">
@@ -88,14 +114,16 @@ export const Import = (props: IProps): ReactElement => {
This process could take a while, if you have a lot of comics, or This process could take a while, if you have a lot of comics, or
are importing over a network connection. are importing over a network connection.
</p> </p>
{JSON.stringify(libraryQueueImportStatus)}
</div> </div>
</article> </article>
<p className="buttons"> <p className="buttons">
<button <button
className={ className={
libraryQueueImportStatus libraryQueueImportStatus === "drained" ||
? "button is-loading is-medium" libraryQueueImportStatus === undefined
: "button is-medium" ? "button is-medium"
: "button is-loading is-medium"
} }
onClick={initiateImport} onClick={initiateImport}
> >
@@ -121,11 +149,13 @@ export const Import = (props: IProps): ReactElement => {
<tbody> <tbody>
<tr> <tr>
<th> <th>
<div className="box has-background-success-light has-text-centered"> {libraryQueueResults && (
<span className="is-size-2 has-text-weight-bold"> <div className="box has-background-success-light has-text-centered">
{libraryQueueResults} <span className="is-size-2 has-text-weight-bold">
</span> {libraryQueueResults}
</div> </span>
</div>
)}
</th> </th>
<td> <td>
{!isUndefined(failedImportJobCount) && ( {!isUndefined(failedImportJobCount) && (
@@ -137,16 +167,7 @@ export const Import = (props: IProps): ReactElement => {
)} )}
</td> </td>
<td> <td>{renderQueueControls(libraryQueueImportStatus)}</td>
<div className="control">
<button
className="button is-warning is-light"
onClick={toggleImport}
>
{isImportQueuePaused ? pauseIconText : playIconText}
</button>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -129,6 +129,8 @@ export const LS_IMPORT = "LS_IMPORT";
export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED"; export const LS_COVER_EXTRACTED = "LS_COVER_EXTRACTED";
export const LS_COVER_EXTRACTION_FAILED = "LS_COVER_EXTRACTION_FAILED"; export const LS_COVER_EXTRACTION_FAILED = "LS_COVER_EXTRACTION_FAILED";
export const LS_COMIC_ADDED = "LS_COMIC_ADDED"; export const LS_COMIC_ADDED = "LS_COMIC_ADDED";
export const LS_IMPORT_QUEUE_DRAINED = "LS_IMPORT_QUEUE_DRAINED";
export const LS_SET_QUEUE_STATUS = "LS_SET_QUEUE_STATUS";
// Settings // Settings
export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS"; export const SETTINGS_CALL_IN_PROGRESS = "SETTINGS_CALL_IN_PROGRESS";

View File

@@ -33,6 +33,8 @@ import {
COMICBOOK_EXTRACTION_SUCCESS, COMICBOOK_EXTRACTION_SUCCESS,
LIBRARY_SERVICE_HEALTH, LIBRARY_SERVICE_HEALTH,
HEALTH_STATUS_TICK, HEALTH_STATUS_TICK,
LS_IMPORT_QUEUE_DRAINED,
LS_SET_QUEUE_STATUS,
} from "../constants/action-types"; } from "../constants/action-types";
import { removeLeadingPeriod } from "../shared/utils/formatting.utils"; import { removeLeadingPeriod } from "../shared/utils/formatting.utils";
import { LIBRARY_SERVICE_HOST } from "../constants/endpoints"; import { LIBRARY_SERVICE_HOST } from "../constants/endpoints";
@@ -43,6 +45,7 @@ const initialState = {
SSCallInProgress: false, SSCallInProgress: false,
imageAnalysisResults: {}, imageAnalysisResults: {},
comicBookExtractionInProgress: false, comicBookExtractionInProgress: false,
LSQueueImportStatus: undefined,
comicBookMetadata: [], comicBookMetadata: [],
comicVolumeGroups: [], comicVolumeGroups: [],
isSocketConnected: false, isSocketConnected: false,
@@ -154,6 +157,7 @@ function fileOpsReducer(state = initialState, action) {
case LS_IMPORT: { case LS_IMPORT: {
return { return {
...state, ...state,
LSQueueImportStatus: "running",
}; };
} }
case LS_COVER_EXTRACTED: { case LS_COVER_EXTRACTED: {
@@ -173,14 +177,32 @@ function fileOpsReducer(state = initialState, action) {
console.log("FAILED", action); console.log("FAILED", action);
return { return {
...state, ...state,
failedImportJobCount: action.failedJobCount, failedJobCount: action.failedJobCount,
}; };
} }
case "LS_IMPORT_QUEUE_DRAINED": { case LS_IMPORT_QUEUE_DRAINED: {
console.log("Queue drained"); console.log("drained");
return { return {
...state, ...state,
LSQueueImportStatus: "drained",
};
}
case "RESTORE_JOB_COUNTS_AFTER_SESSION_RESTORATION": {
console.log(action);
return {
...state,
librarySearchResultCount: action.completedJobCount,
failedJobCount: action.failedJobCount,
};
}
case LS_SET_QUEUE_STATUS: {
console.log(action);
return {
...state,
LSQueueImportStatus: action.data.queueStatus,
}; };
} }