🔌 Socket + RabbitMQ setup for download-client touched folders/files
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { useContext } from "react";
|
||||
import { IFolderData, IExtractedComicBookCoverFile } from "threetwo-ui-typings";
|
||||
import { API_BASE_URI, SOCKET_BASE_URI } from "../constants/endpoints";
|
||||
import {
|
||||
@@ -15,14 +16,8 @@ import {
|
||||
} from "../constants/action-types";
|
||||
import { refineQuery } from "../shared/utils/filenameparser.utils";
|
||||
import sortBy from "array-sort-by";
|
||||
import { io } from "socket.io-client";
|
||||
import {
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
removeAll,
|
||||
} from "react-notification-system-redux";
|
||||
import { success } from "react-notification-system-redux";
|
||||
import { WebSocketContext } from "../context/socket/socket.context";
|
||||
|
||||
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
||||
return axios
|
||||
@@ -51,24 +46,8 @@ export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
||||
* @return {Promise<string>} HTML of the page
|
||||
*/
|
||||
export const fetchComicBookMetadata = (options) => async (dispatch) => {
|
||||
const socket = io(SOCKET_BASE_URI, {
|
||||
reconnectionDelayMax: 10000,
|
||||
secure: false,
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
socket.on("connect", () => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
dispatch({
|
||||
type: RMQ_SOCKET_CONNECTED,
|
||||
isSocketConnected: true,
|
||||
socketId: socket.id,
|
||||
});
|
||||
});
|
||||
socket.on("disconnect", () => {
|
||||
console.log(`disconnect`);
|
||||
});
|
||||
const socket = useContext(WebSocketContext);
|
||||
const extractionOptions = {
|
||||
sourceFolder: options,
|
||||
extractTarget: "cover",
|
||||
targetExtractionFolder: "./userdata/covers",
|
||||
extractionMode: "bulk",
|
||||
@@ -82,7 +61,7 @@ export const fetchComicBookMetadata = (options) => async (dispatch) => {
|
||||
success({
|
||||
// uid: 'once-please', // you can specify your own uid if required
|
||||
title: "Import Started",
|
||||
message: `${socket.id} connected. ${walkedFolders.length} comics scanned.`,
|
||||
message: `<span class="icon-text has-text-success"><i class="fas fa-plug"></i></span> Socket <span class="has-text-info">${socket.id}</span> connected. <strong>${walkedFolders.length}</strong> comics scanned.`,
|
||||
dismissible: "click",
|
||||
position: "tr",
|
||||
autoDismiss: 0,
|
||||
|
||||
@@ -28,6 +28,15 @@ $border-color: red;
|
||||
|
||||
.min {
|
||||
overflow: visible;
|
||||
margin: auto;
|
||||
.tag__custom {
|
||||
height: auto !important;
|
||||
padding: 0.3rem;
|
||||
white-space: unset !important;
|
||||
width: 100%;
|
||||
background-color: #effaf5;
|
||||
color: #257953;
|
||||
}
|
||||
.tags {
|
||||
display: inline;
|
||||
margin-right: 5px;
|
||||
|
||||
@@ -29,6 +29,19 @@ const style = {
|
||||
},
|
||||
tr: {
|
||||
top: "40px",
|
||||
right: "10px",
|
||||
},
|
||||
},
|
||||
Title: {
|
||||
DefaultStyle: {
|
||||
fontSize: "14px",
|
||||
margin: "0 0 5px 0",
|
||||
padding: 0,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
|
||||
success: {
|
||||
color: "hsl(141, 71%, 48%)",
|
||||
},
|
||||
},
|
||||
NotificationItem: {
|
||||
@@ -36,7 +49,7 @@ const style = {
|
||||
success: {
|
||||
// Applied to every notification, regardless of the notification level
|
||||
borderTop: "none",
|
||||
backgroundColor: "none",
|
||||
backgroundColor: "#FFF",
|
||||
borderRadius: "0.4rem",
|
||||
WebkitBoxShadow: "-7px 11px 25px -9px rgba(0, 0, 0, 0.3)",
|
||||
MozBoxShadow: "-7px 11px 25px -9px rgba(0, 0, 0, 0.3)",
|
||||
@@ -53,6 +66,7 @@ export const App = (): ReactElement => {
|
||||
notifications={notifications}
|
||||
style={style}
|
||||
newOnTop={true}
|
||||
allowHTML={true}
|
||||
/>
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
|
||||
@@ -213,10 +213,26 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
|
||||
<dl>
|
||||
<dt>Raw File Details</dt>
|
||||
<dd>{props.data.containedIn}</dd>
|
||||
<dd>{prettyBytes(props.data.fileSize)}</dd>
|
||||
<dd>{props.data.path}</dd>
|
||||
<dd>
|
||||
<span className="tag is-primary">{props.data.extension}</span>
|
||||
<div className="field is-grouped">
|
||||
<div className="control">
|
||||
<div className="tags has-addons">
|
||||
<span className="tag">Size</span>
|
||||
<span className="tag is-info is-light">
|
||||
{prettyBytes(props.data.fileSize)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="control">
|
||||
<div className="tags has-addons">
|
||||
<span className="tag">Extension</span>
|
||||
<span className="tag is-primary is-light">
|
||||
{props.data.extension}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { ReactElement, useCallback } from "react";
|
||||
import { isNil, isUndefined } from "lodash";
|
||||
import { isEmpty, isNil, isUndefined } from "lodash";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { fetchComicBookMetadata } from "../actions/fileops.actions";
|
||||
import { IFolderData } from "threetwo-ui-typings";
|
||||
import { LazyLog, ScrollFollow } from "react-lazylog";
|
||||
import DynamicList, { createCache } from "react-window-dynamic-list";
|
||||
;
|
||||
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
||||
import Loader from "react-loader-spinner";
|
||||
|
||||
interface IProps {
|
||||
matches?: unknown;
|
||||
@@ -32,13 +34,27 @@ export const Import = (props: IProps): ReactElement => {
|
||||
console.log(state);
|
||||
return state.fileOps.isSocketConnected;
|
||||
});
|
||||
|
||||
const importResults = useSelector(
|
||||
(state: RootState) => state.fileOps.comicBookMetadata,
|
||||
);
|
||||
const IMSCallInProgress = useSelector(
|
||||
(state: RootState) => state.fileOps.IMSCallInProgress,
|
||||
);
|
||||
const initiateImport = useCallback(() => {
|
||||
if (typeof props.path !== "undefined") {
|
||||
console.log("asdasd");
|
||||
dispatch(fetchComicBookMetadata(props.path));
|
||||
}
|
||||
}, [dispatch]);
|
||||
const cache = createCache();
|
||||
const renderRow = ({ index, style }) => (
|
||||
<li className="is-size-7" style={style}>
|
||||
<strong>{importResults[index].comicBookCoverMetadata.name} </strong>
|
||||
<br />
|
||||
{importResults[index].comicBookCoverMetadata.path}
|
||||
<hr />
|
||||
</li>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
@@ -78,6 +94,37 @@ export const Import = (props: IProps): ReactElement => {
|
||||
<span>Import and Tag</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
{!isEmpty(importResults) ? (
|
||||
<>
|
||||
<h3>Import Results</h3>
|
||||
<hr />
|
||||
<ul>
|
||||
<DynamicList
|
||||
data={importResults}
|
||||
cache={cache}
|
||||
height={800}
|
||||
width={"100%"}
|
||||
lazyMeasurement={false}
|
||||
>
|
||||
{renderRow}
|
||||
</DynamicList>
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<div className="progress-indicator-container">
|
||||
<div className="indicator">
|
||||
<Loader
|
||||
type="MutatingDots"
|
||||
color="#CCC"
|
||||
secondaryColor="#999"
|
||||
height={100}
|
||||
width={100}
|
||||
visible={IMSCallInProgress}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
|
||||
37
src/client/context/socket/socket.context.tsx
Normal file
37
src/client/context/socket/socket.context.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React, { createContext } from "react";
|
||||
import io, { Socket } from "socket.io-client";
|
||||
import { SOCKET_BASE_URI } from "../../constants/endpoints";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { RMQ_SOCKET_CONNECTED } from "../../constants/action-types";
|
||||
|
||||
const WebSocketContext = createContext(null);
|
||||
export const WebSocketProvider = ({ children }) => {
|
||||
let socket: Socket;
|
||||
let ws;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!socket) {
|
||||
socket = io(SOCKET_BASE_URI);
|
||||
|
||||
socket.on("connect", () => {
|
||||
dispatch({
|
||||
type: RMQ_SOCKET_CONNECTED,
|
||||
isSocketConnected: true,
|
||||
socketId: socket.id,
|
||||
});
|
||||
});
|
||||
socket.on("disconnect", () => {
|
||||
console.log(`disconnect`);
|
||||
});
|
||||
|
||||
ws = {
|
||||
socket: socket,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<WebSocketContext.Provider value={ws}>{children}</WebSocketContext.Provider>
|
||||
);
|
||||
};
|
||||
export { WebSocketContext };
|
||||
export default WebSocketProvider;
|
||||
@@ -2,6 +2,9 @@ import * as React from "react";
|
||||
import { render } from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { ConnectedRouter } from "connected-react-router";
|
||||
import WebSocketProvider, {
|
||||
WebSocketContext,
|
||||
} from "./context/socket/socket.context";
|
||||
import configureStore, { history } from "./store/index";
|
||||
import App from "./components/App";
|
||||
|
||||
@@ -10,9 +13,11 @@ const rootEl = document.getElementById("root");
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
<WebSocketProvider>
|
||||
<ConnectedRouter history={history}>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
</WebSocketProvider>
|
||||
</Provider>,
|
||||
rootEl,
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
||||
} from "../constants/action-types";
|
||||
const initialState = {
|
||||
dataTransferred: false,
|
||||
IMSCallInProgress: false,
|
||||
comicBookMetadata: [],
|
||||
isSocketConnected: false,
|
||||
isComicVineMetadataImportInProgress: false,
|
||||
@@ -26,7 +26,7 @@ function fileOpsReducer(state = initialState, action) {
|
||||
return {
|
||||
...state,
|
||||
comicBookMetadata: [...state.comicBookMetadata, action.data],
|
||||
dataTransferred: true,
|
||||
IMSCallInProgress: false,
|
||||
};
|
||||
|
||||
case RMQ_SOCKET_CONNECTED:
|
||||
@@ -34,6 +34,7 @@ function fileOpsReducer(state = initialState, action) {
|
||||
...state,
|
||||
isSocketConnected: action.isSocketConnected,
|
||||
socketId: action.socketId,
|
||||
IMSCallInProgress: true,
|
||||
};
|
||||
case IMS_RAW_IMPORT_SUCCESSFUL:
|
||||
return {
|
||||
|
||||
@@ -4,9 +4,10 @@ export const detectTradePaperbacks = (deck): any => {
|
||||
const paperback = [
|
||||
/((trade)?\s?(paperback)|(tpb))/gim, // https://regex101.com/r/FhuowT/1
|
||||
/(hard\s?cover)\s?(collect((ion)|(ed)|(ing)))/gim, //https://regex101.com/r/eFJVRM/1
|
||||
/(collected\s?editions)/gim, // https://regex101.com/r/40pAm5/1
|
||||
/(?:collects|issues|issue)/gim,
|
||||
];
|
||||
const miniSeries = [
|
||||
|
||||
]
|
||||
const matches = paperback
|
||||
.map((regex) => {
|
||||
return deck.match(regex);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import express, { Request, Response, Router, Express } from "express";
|
||||
import bodyParser from "body-parser";
|
||||
import { createServer } from "http";
|
||||
import { Server, Socket } from "socket.io";
|
||||
import { Server } from "socket.io";
|
||||
import router from "./route";
|
||||
const amqp = require("amqplib/callback_api");
|
||||
|
||||
// call express
|
||||
const app: Express = express(); // define our app using express
|
||||
@@ -42,6 +43,35 @@ io.on("connection", (socket) => {
|
||||
});
|
||||
});
|
||||
|
||||
amqp.connect("amqp://localhost", (error0, connection) => {
|
||||
if (error0) {
|
||||
throw error0;
|
||||
}
|
||||
connection.createChannel((error1, channel) => {
|
||||
if (error1) {
|
||||
throw error1;
|
||||
}
|
||||
const queue = "comicBookCovers";
|
||||
channel.assertQueue(queue, {
|
||||
durable: false,
|
||||
});
|
||||
|
||||
console.log(`Connected to ${queue} queue.`);
|
||||
console.log(`Waiting for comic book cover data in ${queue}`);
|
||||
|
||||
channel.consume(
|
||||
queue,
|
||||
(data) => {
|
||||
//Socket Trigger All Clients
|
||||
io.sockets.emit("coverExtracted", JSON.parse(data.content.toString()));
|
||||
},
|
||||
{
|
||||
noAck: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// socket server
|
||||
httpServer.listen(8051);
|
||||
console.log(`Socket server is listening on 8051`);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import router from "../router";
|
||||
import { Request, Response } from "express";
|
||||
const amqp = require("amqplib/callback_api");
|
||||
import axios from "axios";
|
||||
import { io } from "../../index";
|
||||
|
||||
router.route("/getComicCovers").post(async (req: Request, res: Response) => {
|
||||
typeof req.body === "object" ? req.body : {};
|
||||
@@ -14,41 +12,7 @@ router.route("/getComicCovers").post(async (req: Request, res: Response) => {
|
||||
walkedFolders: req.body.walkedFolders,
|
||||
},
|
||||
});
|
||||
const queueConsumer = amqp.connect(
|
||||
"amqp://localhost",
|
||||
(error0, connection) => {
|
||||
if (error0) {
|
||||
throw error0;
|
||||
}
|
||||
connection.createChannel((error1, channel) => {
|
||||
if (error1) {
|
||||
throw error1;
|
||||
}
|
||||
const queue = "comicBookCovers";
|
||||
channel.assertQueue(queue, {
|
||||
durable: false,
|
||||
});
|
||||
|
||||
console.log(`Connected to ${queue} queue.`);
|
||||
console.log(`Waiting for comic book cover data in ${queue}`);
|
||||
|
||||
channel.consume(
|
||||
queue,
|
||||
(data) => {
|
||||
//Socket Trigger All Clients
|
||||
io.sockets.emit(
|
||||
"coverExtracted",
|
||||
JSON.parse(data.content.toString()),
|
||||
);
|
||||
},
|
||||
{
|
||||
noAck: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
res.send({ queue: queueConsumer });
|
||||
res.send({ po: "jo" });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user