🐰 RabbitMQ for enqueuing comic import jobs
This commit is contained in:
@@ -29,6 +29,7 @@
|
|||||||
"@types/socket.io-client": "^3.0.0",
|
"@types/socket.io-client": "^3.0.0",
|
||||||
"@types/through2": "^2.0.36",
|
"@types/through2": "^2.0.36",
|
||||||
"airdcpp-apisocket": "^2.4.4",
|
"airdcpp-apisocket": "^2.4.4",
|
||||||
|
"amqplib": "^0.8.0",
|
||||||
"array-sort-by": "^1.2.1",
|
"array-sort-by": "^1.2.1",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"better-docs": "^2.3.2",
|
"better-docs": "^2.3.2",
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
|
"react-hot-toast": "^2.1.1",
|
||||||
"react-loader-spinner": "^4.0.0",
|
"react-loader-spinner": "^4.0.0",
|
||||||
"react-select": "^4.3.1",
|
"react-select": "^4.3.1",
|
||||||
"react-sliding-pane": "^7.0.0",
|
"react-sliding-pane": "^7.0.0",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { IFolderData, IExtractedComicBookCoverFile } from "threetwo-ui-typings";
|
import { IFolderData, IExtractedComicBookCoverFile } from "threetwo-ui-typings";
|
||||||
import { API_BASE_URI, SOCKET_BASE_URI } from "../constants/endpoints";
|
import { API_BASE_URI, SOCKET_BASE_URI } from "../constants/endpoints";
|
||||||
import { io } from "socket.io-client";
|
|
||||||
import {
|
import {
|
||||||
IMS_COMICBOOK_METADATA_FETCHED,
|
IMS_COMICBOOK_METADATA_FETCHED,
|
||||||
IMS_SOCKET_CONNECTION_CONNECTED,
|
IMS_SOCKET_CONNECTION_CONNECTED,
|
||||||
@@ -12,9 +11,11 @@ import {
|
|||||||
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
IMS_CV_METADATA_IMPORT_CALL_IN_PROGRESS,
|
||||||
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
||||||
IMS_CV_METADATA_IMPORT_FAILED,
|
IMS_CV_METADATA_IMPORT_FAILED,
|
||||||
|
RMQ_SOCKET_CONNECTED,
|
||||||
} from "../constants/action-types";
|
} from "../constants/action-types";
|
||||||
import { refineQuery } from "../shared/utils/filenameparser.utils";
|
import { refineQuery } from "../shared/utils/filenameparser.utils";
|
||||||
import sortBy from "array-sort-by";
|
import sortBy from "array-sort-by";
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
|
||||||
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
||||||
return axios
|
return axios
|
||||||
@@ -43,6 +44,22 @@ export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
|||||||
* @return {Promise<string>} HTML of the page
|
* @return {Promise<string>} HTML of the page
|
||||||
*/
|
*/
|
||||||
export const fetchComicBookMetadata = (options) => async (dispatch) => {
|
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 extractionOptions = {
|
const extractionOptions = {
|
||||||
sourceFolder: options,
|
sourceFolder: options,
|
||||||
extractTarget: "cover",
|
extractTarget: "cover",
|
||||||
@@ -55,37 +72,20 @@ export const fetchComicBookMetadata = (options) => async (dispatch) => {
|
|||||||
};
|
};
|
||||||
const walkedFolders = await walkFolder("./comics");
|
const walkedFolders = await walkFolder("./comics");
|
||||||
|
|
||||||
const socket = io(SOCKET_BASE_URI, {
|
await axios
|
||||||
reconnectionDelayMax: 10000,
|
.request({
|
||||||
});
|
url: "http://localhost:8050/api/getComicCovers",
|
||||||
|
method: "POST",
|
||||||
socket.on("connect", () => {
|
data: {
|
||||||
console.log(`connect ${socket.id}`);
|
extractionOptions,
|
||||||
dispatch({
|
walkedFolders,
|
||||||
type: IMS_SOCKET_CONNECTION_CONNECTED,
|
},
|
||||||
socketConnected: true,
|
})
|
||||||
|
.then((response) => {
|
||||||
|
console.log("Response from import call", response);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
socket.on("coverExtracted", (data) => {
|
||||||
console.log(`disconnect`);
|
|
||||||
});
|
|
||||||
socket.emit("importComicsToDB", {
|
|
||||||
action: "getComicCovers",
|
|
||||||
params: {
|
|
||||||
extractionOptions,
|
|
||||||
walkedFolders,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("comicBookCoverMetadata", (data: IExtractedComicBookCoverFile) => {
|
|
||||||
dispatch({
|
|
||||||
type: IMS_COMICBOOK_METADATA_FETCHED,
|
|
||||||
data,
|
|
||||||
dataTransferred: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
socket.on("comicBookExists", (data) => {
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import { isUndefined } from "lodash";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { fetchComicBookMetadata } from "../actions/fileops.actions";
|
import { fetchComicBookMetadata } from "../actions/fileops.actions";
|
||||||
import { IFolderData } from "threetwo-ui-typings";
|
import { IFolderData } from "threetwo-ui-typings";
|
||||||
import { io, Socket } from "socket.io-client";
|
|
||||||
import { SOCKET_BASE_URI } from "../constants/endpoints";
|
|
||||||
import DynamicList, { createCache } from "react-window-dynamic-list";
|
import DynamicList, { createCache } from "react-window-dynamic-list";
|
||||||
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
matches: unknown;
|
matches: unknown;
|
||||||
@@ -18,7 +17,6 @@ interface IState {
|
|||||||
searchPaneIndex: number;
|
searchPaneIndex: number;
|
||||||
fileOps: any;
|
fileOps: any;
|
||||||
}
|
}
|
||||||
let socket: Socket;
|
|
||||||
class Import extends React.Component<IProps, IState> {
|
class Import extends React.Component<IProps, IState> {
|
||||||
/**
|
/**
|
||||||
* Returns the average of two numbers.
|
* Returns the average of two numbers.
|
||||||
@@ -47,16 +45,17 @@ class Import extends React.Component<IProps, IState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public initiateSocketConnection = () => {
|
public initiateImport = () => {
|
||||||
if (typeof this.props.path !== "undefined") {
|
if (typeof this.props.path !== "undefined") {
|
||||||
socket = io(SOCKET_BASE_URI, {
|
|
||||||
reconnectionDelayMax: 10000,
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("connect", () => {
|
|
||||||
console.log(`connect ${socket.id}`);
|
|
||||||
});
|
|
||||||
this.props.fetchComicMetadata();
|
this.props.fetchComicMetadata();
|
||||||
|
toast.custom(
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-content">Saokaaate</div>
|
||||||
|
</div>,
|
||||||
|
{
|
||||||
|
position: "top-right",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,11 +112,9 @@ class Import extends React.Component<IProps, IState> {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
<Toaster />
|
||||||
<p className="buttons">
|
<p className="buttons">
|
||||||
<button
|
<button className="button is-medium" onClick={this.initiateImport}>
|
||||||
className="button is-medium"
|
|
||||||
onClick={this.initiateSocketConnection}
|
|
||||||
>
|
|
||||||
<span className="icon">
|
<span className="icon">
|
||||||
<i className="fas fa-file-import"></i>
|
<i className="fas fa-file-import"></i>
|
||||||
</span>
|
</span>
|
||||||
@@ -132,18 +129,7 @@ class Import extends React.Component<IProps, IState> {
|
|||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{!isUndefined(this.state.folderWalkResults) ? (
|
{!isUndefined(this.state.folderWalkResults) ? <div></div> : null}
|
||||||
<div>
|
|
||||||
<DynamicList
|
|
||||||
data={this.props.covers}
|
|
||||||
cache={this.cache}
|
|
||||||
height={1000}
|
|
||||||
width={"100%"}
|
|
||||||
>
|
|
||||||
{this.renderRow}
|
|
||||||
</DynamicList>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -154,7 +140,7 @@ function mapStateToProps(state: IState) {
|
|||||||
console.log("state", state);
|
console.log("state", state);
|
||||||
return {
|
return {
|
||||||
// matches: state.comicInfo.searchResults,
|
// matches: state.comicInfo.searchResults,
|
||||||
covers: state.fileOps.comicBookMetadata,
|
// covers: state.fileOps.comicBookMetadata,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,4 +151,3 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Import);
|
export default connect(mapStateToProps, mapDispatchToProps)(Import);
|
||||||
export { socket };
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { RootState } from "threetwo-ui-typings";
|
|
||||||
|
|
||||||
const Navbar: React.FunctionComponent = (props) => {
|
const Navbar: React.FunctionComponent = (props) => {
|
||||||
const socketConnection = useSelector((state: RootState) => state.fileOps);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="navbar is-fixed-top">
|
<nav className="navbar is-fixed-top">
|
||||||
@@ -195,13 +192,6 @@ const Navbar: React.FunctionComponent = (props) => {
|
|||||||
<a className="navbar-item is-hidden-desktop-only"></a>
|
<a className="navbar-item is-hidden-desktop-only"></a>
|
||||||
<div className="navbar-item">
|
<div className="navbar-item">
|
||||||
<div className="field is-grouped">
|
<div className="field is-grouped">
|
||||||
<p className="control">
|
|
||||||
{socketConnection.socketConnected ? (
|
|
||||||
<span className="icon is-small has-text-success">
|
|
||||||
<i className="fas fa-plug"></i>
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</p>
|
|
||||||
<p className="control">
|
<p className="control">
|
||||||
<Link to="/settings" className="navbar-item">
|
<Link to="/settings" className="navbar-item">
|
||||||
Settings
|
Settings
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ export const CV_CLEANUP = "CV_CLEANUP";
|
|||||||
export const CV_API_GENERIC_FAILURE = "CV_API_GENERIC_FAILURE";
|
export const CV_API_GENERIC_FAILURE = "CV_API_GENERIC_FAILURE";
|
||||||
|
|
||||||
export const IMS_COMICBOOK_METADATA_FETCHED = "IMS_SOCKET_DATA_FETCHED";
|
export const IMS_COMICBOOK_METADATA_FETCHED = "IMS_SOCKET_DATA_FETCHED";
|
||||||
export const IMS_SOCKET_CONNECTION_CONNECTED =
|
|
||||||
"IMS_SOCKET_CONNECTION_CONNECTED";
|
// rabbitmq
|
||||||
export const IMS_SOCKET_CONNECTION_DISCONNECTED =
|
export const RMQ_SOCKET_CONNECTED = "RMQ_SOCKET_CONNECTED";
|
||||||
"IMS_SOCKET_CONNECTION_DISCONNECTED";
|
export const RMQ_SOCKET_DISCONNECTED = "RMQ_SOCKET_DISCONNECTED";
|
||||||
export const IMS_SOCKET_ERROR = "IMS_SOCKET_ERROR";
|
export const RMQ_SOCKET_ERROR = "RMQ_SOCKET_ERROR";
|
||||||
|
|
||||||
export const IMS_RAW_IMPORT_SUCCESSFUL = "IMS_RAW_IMPORT_SUCCESSFUL";
|
export const IMS_RAW_IMPORT_SUCCESSFUL = "IMS_RAW_IMPORT_SUCCESSFUL";
|
||||||
export const IMS_RAW_IMPORT_FAILED = "IMS_RAW_IMPORT_FAILED";
|
export const IMS_RAW_IMPORT_FAILED = "IMS_RAW_IMPORT_FAILED";
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export const COMICBOOKINFO_SERVICE_URI = "http://localhost:3080/api/comicvine/";
|
export const COMICBOOKINFO_SERVICE_URI = "http://localhost:3080/api/comicvine/";
|
||||||
export const API_BASE_URI = "http://localhost:8050/api/";
|
export const API_BASE_URI = "http://localhost:8050/api/";
|
||||||
export const SOCKET_BASE_URI = "ws://localhost:3000/";
|
export const SOCKET_BASE_URI = "ws://localhost:8051/";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
IMS_SOCKET_CONNECTION_CONNECTED,
|
RMQ_SOCKET_CONNECTED,
|
||||||
IMS_SOCKET_CONNECTION_DISCONNECTED,
|
RMQ_SOCKET_DISCONNECTED,
|
||||||
IMS_COMICBOOK_METADATA_FETCHED,
|
IMS_COMICBOOK_METADATA_FETCHED,
|
||||||
IMS_SOCKET_ERROR,
|
RMQ_SOCKET_ERROR,
|
||||||
IMS_RAW_IMPORT_SUCCESSFUL,
|
IMS_RAW_IMPORT_SUCCESSFUL,
|
||||||
IMS_RAW_IMPORT_FAILED,
|
IMS_RAW_IMPORT_FAILED,
|
||||||
IMS_RECENT_COMICS_FETCHED,
|
IMS_RECENT_COMICS_FETCHED,
|
||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
dataTransferred: false,
|
dataTransferred: false,
|
||||||
comicBookMetadata: [],
|
comicBookMetadata: [],
|
||||||
socketConnected: false,
|
isSocketConnected: false,
|
||||||
isComicVineMetadataImportInProgress: false,
|
isComicVineMetadataImportInProgress: false,
|
||||||
comicVineMetadataImportError: {},
|
comicVineMetadataImportError: {},
|
||||||
rawImportError: {},
|
rawImportError: {},
|
||||||
@@ -29,10 +29,10 @@ function fileOpsReducer(state = initialState, action) {
|
|||||||
dataTransferred: true,
|
dataTransferred: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
case IMS_SOCKET_CONNECTION_CONNECTED:
|
case RMQ_SOCKET_CONNECTED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
socketConnected: action.socketConnected,
|
isSocketConnected: action.isSocketConnected,
|
||||||
};
|
};
|
||||||
case IMS_RAW_IMPORT_SUCCESSFUL:
|
case IMS_RAW_IMPORT_SUCCESSFUL:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import express, { Request, Response, Router, Express } from "express";
|
import express, { Request, Response, Router, Express } from "express";
|
||||||
import bodyParser from "body-parser";
|
import bodyParser from "body-parser";
|
||||||
import { basename, extname, join } from "path";
|
import { createServer } from "http";
|
||||||
import { lookup } from "mime-types";
|
import { Server, Socket } from "socket.io";
|
||||||
import { promises as fs } from "fs";
|
import router from "./route";
|
||||||
import { responseStream } from "http-response-stream";
|
|
||||||
import { isUndefined } from "lodash";
|
|
||||||
import { buildAsync } from "calibre-opds";
|
|
||||||
import initMain from "calibre-opds/lib/index";
|
|
||||||
import { EnumLinkRel } from "opds-extra/lib/const";
|
|
||||||
import { async as FastGlob } from "@bluelovers/fast-glob/bluebird";
|
|
||||||
import { Entry, Feed } from "opds-extra/lib/v1";
|
|
||||||
import { Link } from "opds-extra/lib/v1/core";
|
|
||||||
|
|
||||||
// call express
|
// call express
|
||||||
const app: Express = express(); // define our app using express
|
const app: Express = express(); // define our app using express
|
||||||
const router = Router();
|
|
||||||
|
const httpServer = createServer();
|
||||||
|
export const io = new Server(httpServer, {});
|
||||||
|
|
||||||
// configure app to use bodyParser for
|
// configure app to use bodyParser for
|
||||||
// Getting data from body of requests
|
// Getting data from body of requests
|
||||||
@@ -27,78 +21,28 @@ const port: number = Number(process.env.PORT) || 8050; // set our port
|
|||||||
app.use(express.static("dist"));
|
app.use(express.static("dist"));
|
||||||
app.use(express.static("public"));
|
app.use(express.static("public"));
|
||||||
|
|
||||||
export const opdsRouter = () => {
|
|
||||||
const path_of_books = "/Users/rishi/work/threetwo/src/server/comics";
|
|
||||||
router.use("/opds", async (req, res, next) => {
|
|
||||||
return buildAsync(
|
|
||||||
initMain({
|
|
||||||
title: `title`,
|
|
||||||
subtitle: `subtitle`,
|
|
||||||
icon: "/favicon.ico",
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
async (feed: Feed) => {
|
|
||||||
feed.books = feed.books || [];
|
|
||||||
await FastGlob(["*.cbr", "*.cbz", "*.cb7", "*.cba", "*.cbt"], {
|
|
||||||
cwd: path_of_books,
|
|
||||||
}).each((file) => {
|
|
||||||
const ext = extname(file);
|
|
||||||
const title = basename(file, ext);
|
|
||||||
const href = encodeURI(`/file/${file}`);
|
|
||||||
const type = lookup(ext) || "application/octet-stream";
|
|
||||||
|
|
||||||
const entry = Entry.deserialize<Entry>({
|
|
||||||
title,
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
rel: EnumLinkRel.ACQUISITION,
|
|
||||||
href,
|
|
||||||
type,
|
|
||||||
} as Link,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isUndefined(feed) && !isUndefined(feed.books)) {
|
|
||||||
console.log("haramzada", feed.books);
|
|
||||||
feed.books.push(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return feed;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
).then((feed) => {
|
|
||||||
res.setHeader("Content-Type", "application/xml");
|
|
||||||
return res.end(feed.toXML());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.use("/file/*", async (req, res) => {
|
|
||||||
const file: string = req.params[0];
|
|
||||||
const ext = extname(file);
|
|
||||||
|
|
||||||
if ([".cbr", ".cbz", ".cb7", ".cba", ".cbt"].includes(ext)) {
|
|
||||||
const content = await fs.readFile(join(path_of_books, file));
|
|
||||||
const mime = lookup(ext) || "application/octet-stream";
|
|
||||||
res.set("Content-Type", mime);
|
|
||||||
return responseStream(res, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(404).end(`'${file}' not exists`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
|
||||||
};
|
|
||||||
|
|
||||||
app.get("/", (req: Request, res: Response) => {
|
app.get("/", (req: Request, res: Response) => {
|
||||||
console.log("sending index.html");
|
console.log("sending index.html");
|
||||||
res.sendFile("/dist/index.html");
|
res.sendFile("/dist/index.html");
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(opdsRouter());
|
|
||||||
|
|
||||||
// REGISTER ROUTES
|
// REGISTER ROUTES
|
||||||
// all of the routes will be prefixed with /api
|
// all of the routes will be prefixed with /api
|
||||||
|
const routes: Router[] = Object.values(router);
|
||||||
|
app.use("/api", routes);
|
||||||
|
|
||||||
app.listen(port);
|
app.listen(port);
|
||||||
console.log(`App listening on ${port}`);
|
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
console.log("Socket connected");
|
||||||
|
|
||||||
|
//Whenever someone disconnects this piece of code executed
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log("Socket disconnected");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// socket server
|
||||||
|
httpServer.listen(8051);
|
||||||
|
console.log(`Socket server is listening on 8051`);
|
||||||
|
console.log(`Server is listening on ${port}`);
|
||||||
|
|||||||
4
src/server/route/index.ts
Normal file
4
src/server/route/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import extra from "./routes/importComics.routes";
|
||||||
|
import opds from "./routes/opds.routes";
|
||||||
|
|
||||||
|
export default { extra, opds };
|
||||||
54
src/server/route/routes/importComics.routes.ts
Normal file
54
src/server/route/routes/importComics.routes.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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 : {};
|
||||||
|
await axios.request({
|
||||||
|
url: "http://localhost:3000/api/import/importComicsToDB",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
extractionOptions: req.body.extractionOptions,
|
||||||
|
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}`);
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
73
src/server/route/routes/opds.routes.ts
Normal file
73
src/server/route/routes/opds.routes.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { basename, extname, join } from "path";
|
||||||
|
import { lookup } from "mime-types";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { responseStream } from "http-response-stream";
|
||||||
|
import { isUndefined } from "lodash";
|
||||||
|
import { buildAsync } from "calibre-opds";
|
||||||
|
import initMain from "calibre-opds/lib/index";
|
||||||
|
import { EnumLinkRel } from "opds-extra/lib/const";
|
||||||
|
import { async as FastGlob } from "@bluelovers/fast-glob/bluebird";
|
||||||
|
import { Entry, Feed } from "opds-extra/lib/v1";
|
||||||
|
import { Link } from "opds-extra/lib/v1/core";
|
||||||
|
import router from "../router";
|
||||||
|
|
||||||
|
const path_of_books = "/Users/rishi/work/threetwo/src/server/comics";
|
||||||
|
router.use("/opds", async (req, res, next) => {
|
||||||
|
return buildAsync(
|
||||||
|
initMain({
|
||||||
|
title: `title`,
|
||||||
|
subtitle: `subtitle`,
|
||||||
|
icon: "/favicon.ico",
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
async (feed: Feed) => {
|
||||||
|
feed.books = feed.books || [];
|
||||||
|
await FastGlob(["*.cbr", "*.cbz", "*.cb7", "*.cba", "*.cbt"], {
|
||||||
|
cwd: path_of_books,
|
||||||
|
}).each((file) => {
|
||||||
|
const ext = extname(file);
|
||||||
|
const title = basename(file, ext);
|
||||||
|
const href = encodeURI(`/file/${file}`);
|
||||||
|
const type = lookup(ext) || "application/octet-stream";
|
||||||
|
|
||||||
|
const entry = Entry.deserialize<Entry>({
|
||||||
|
title,
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: EnumLinkRel.ACQUISITION,
|
||||||
|
href,
|
||||||
|
type,
|
||||||
|
} as Link,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isUndefined(feed) && !isUndefined(feed.books)) {
|
||||||
|
console.log("haramzada", feed.books);
|
||||||
|
feed.books.push(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return feed;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
).then((feed) => {
|
||||||
|
res.setHeader("Content-Type", "application/xml");
|
||||||
|
return res.end(feed.toXML());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use("/file/*", async (req, res) => {
|
||||||
|
const file: string = req.params[0];
|
||||||
|
const ext = extname(file);
|
||||||
|
|
||||||
|
if ([".cbr", ".cbz", ".cb7", ".cba", ".cbt"].includes(ext)) {
|
||||||
|
const content = await fs.readFile(join(path_of_books, file));
|
||||||
|
const mime = lookup(ext) || "application/octet-stream";
|
||||||
|
res.set("Content-Type", mime);
|
||||||
|
return responseStream(res, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(404).end(`'${file}' not exists`);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
69
yarn.lock
69
yarn.lock
@@ -2341,6 +2341,18 @@ amdefine@>=0.0.4:
|
|||||||
resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz"
|
||||||
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
|
integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
|
||||||
|
|
||||||
|
amqplib@^0.8.0:
|
||||||
|
version "0.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.8.0.tgz#088d10bc61cc5ac5a49ac72033c7ac66c23aeb61"
|
||||||
|
integrity sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==
|
||||||
|
dependencies:
|
||||||
|
bitsyntax "~0.1.0"
|
||||||
|
bluebird "^3.7.2"
|
||||||
|
buffer-more-ints "~1.0.0"
|
||||||
|
readable-stream "1.x >=1.1.9"
|
||||||
|
safe-buffer "~5.2.1"
|
||||||
|
url-parse "~1.5.1"
|
||||||
|
|
||||||
ansi-align@^2.0.0:
|
ansi-align@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz"
|
resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz"
|
||||||
@@ -3108,6 +3120,15 @@ bindings@^1.5.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
file-uri-to-path "1.0.0"
|
file-uri-to-path "1.0.0"
|
||||||
|
|
||||||
|
bitsyntax@~0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bitsyntax/-/bitsyntax-0.1.0.tgz#b0c59acef03505de5a2ed62a2f763c56ae1d6205"
|
||||||
|
integrity sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==
|
||||||
|
dependencies:
|
||||||
|
buffer-more-ints "~1.0.0"
|
||||||
|
debug "~2.6.9"
|
||||||
|
safe-buffer "~5.1.2"
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz"
|
resolved "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz"
|
||||||
@@ -3301,6 +3322,11 @@ buffer-indexof@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz"
|
resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz"
|
||||||
integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
|
integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
|
||||||
|
|
||||||
|
buffer-more-ints@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422"
|
||||||
|
integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==
|
||||||
|
|
||||||
buffer@^5.2.1, buffer@^5.5.0:
|
buffer@^5.2.1, buffer@^5.5.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz"
|
resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz"
|
||||||
@@ -4377,7 +4403,7 @@ de-indent@^1.0.2:
|
|||||||
resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz"
|
||||||
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9, debug@~2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
@@ -6280,6 +6306,11 @@ globule@^1.0.0:
|
|||||||
lodash "~4.17.10"
|
lodash "~4.17.10"
|
||||||
minimatch "~3.0.2"
|
minimatch "~3.0.2"
|
||||||
|
|
||||||
|
goober@^2.0.35:
|
||||||
|
version "2.0.41"
|
||||||
|
resolved "https://registry.yarnpkg.com/goober/-/goober-2.0.41.tgz#0a3d786ff9917bcf2a096eef703bf717838cbec9"
|
||||||
|
integrity sha512-kwjegMT5018zWydhOQlQneCgCtrKJaPsru7TaBWmTYV0nsMeUrM6L6O8JmNYb9UbPMgWcmtf+9p4Y3oJabIH1A==
|
||||||
|
|
||||||
got@^6.7.1:
|
got@^6.7.1:
|
||||||
version "6.7.1"
|
version "6.7.1"
|
||||||
resolved "https://registry.npmjs.org/got/-/got-6.7.1.tgz"
|
resolved "https://registry.npmjs.org/got/-/got-6.7.1.tgz"
|
||||||
@@ -6897,7 +6928,7 @@ inflight@^1.0.4:
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
|
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
@@ -10876,6 +10907,13 @@ react-hot-loader@^4.13.0:
|
|||||||
shallowequal "^1.1.0"
|
shallowequal "^1.1.0"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
|
react-hot-toast@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.1.1.tgz#56409ab406b534e9e58274cf98d80355ba0fdda0"
|
||||||
|
integrity sha512-Odrp4wue0fHh0pOfZt5H+9nWCMtqs3wdlFSzZPp7qsxfzmbE26QmGWIh6hG43CukiPeOjA8WQhBJU8JwtWvWbQ==
|
||||||
|
dependencies:
|
||||||
|
goober "^2.0.35"
|
||||||
|
|
||||||
react-input-autosize@^3.0.0:
|
react-input-autosize@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
|
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
|
||||||
@@ -11109,6 +11147,16 @@ read@1, read@^1.0.7, read@~1.0.1, read@~1.0.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mute-stream "~0.0.4"
|
mute-stream "~0.0.4"
|
||||||
|
|
||||||
|
"readable-stream@1.x >=1.1.9":
|
||||||
|
version "1.1.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||||
|
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.1"
|
||||||
|
isarray "0.0.1"
|
||||||
|
string_decoder "~0.10.x"
|
||||||
|
|
||||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
|
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
|
||||||
version "2.3.7"
|
version "2.3.7"
|
||||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
|
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
|
||||||
@@ -11547,12 +11595,12 @@ rxjs@^6.5.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@~5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2:
|
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
@@ -12444,6 +12492,11 @@ string_decoder@^1.1.1, string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
|
string_decoder@~0.10.x:
|
||||||
|
version "0.10.31"
|
||||||
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||||
|
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
|
||||||
|
|
||||||
stringify-package@^1.0.1:
|
stringify-package@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz"
|
||||||
@@ -13362,6 +13415,14 @@ url-parse@^1.4.3, url-parse@^1.5.1:
|
|||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
url-parse@~1.5.1:
|
||||||
|
version "1.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
|
||||||
|
integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
|
||||||
|
dependencies:
|
||||||
|
querystringify "^2.1.1"
|
||||||
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
url-to-options@^1.0.1:
|
url-to-options@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user