🍼 Cleaned up the OPDS feed

This commit is contained in:
2021-09-23 18:11:20 -07:00
parent 99e25b6cbb
commit bbde67bb11
9 changed files with 158 additions and 74 deletions

View File

@@ -71,6 +71,7 @@
"websocket": "^1.0.34", "websocket": "^1.0.34",
"ws": "^7.5.3", "ws": "^7.5.3",
"ws-calibre": "https://github.com/bluelovers/ws-calibre", "ws-calibre": "https://github.com/bluelovers/ws-calibre",
"xml2js": "^0.4.23",
"xregexp": "^5.0.2" "xregexp": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -33,7 +33,6 @@ export const search = (data: SearchData) => async (dispatch) => {
await SocketService.connect("admin", "password", true); await SocketService.connect("admin", "password", true);
} }
const instance: SearchInstance = await SocketService.post("search"); const instance: SearchInstance = await SocketService.post("search");
console.log(instance);
dispatch({ dispatch({
type: AIRDCPP_SEARCH_IN_PROGRESS, type: AIRDCPP_SEARCH_IN_PROGRESS,
}); });
@@ -98,7 +97,7 @@ export const search = (data: SearchData) => async (dispatch) => {
// Finally, perform the actual search // Finally, perform the actual search
await SocketService.post(`search/${instance.id}/hub_search`, data); await SocketService.post(`search/${instance.id}/hub_search`, data);
} catch (error) { } catch (error) {
console.log("ERO", error); console.log(error);
throw error; throw error;
} }
}; };
@@ -185,7 +184,6 @@ export const getBundlesForComic =
if (!SocketService.isConnected()) { if (!SocketService.isConnected()) {
await SocketService.connect("admin", "password", true); await SocketService.connect("admin", "password", true);
} }
// const bundles = await SocketService.get("queue/bundles/0/50");
const comicObject = await axios({ const comicObject = await axios({
method: "POST", method: "POST",
url: "http://localhost:3000/api/import/getComicBookById", url: "http://localhost:3000/api/import/getComicBookById",

View File

@@ -35,7 +35,7 @@ $border-color: red;
width: auto; width: auto;
.recent-comics-column { .recent-comics-column {
padding-left: 25px; /* gutter size */ padding-left: 22px; /* gutter size */
background-clip: padding-box; background-clip: padding-box;
& > div { & > div {
/* change div to reference your elements you put in <Masonry> */ /* change div to reference your elements you put in <Masonry> */
@@ -82,7 +82,7 @@ $border-color: red;
width: auto; width: auto;
} }
.volumes-grid-column { .volumes-grid-column {
padding-left: 25px; /* gutter size */ padding-left: 22px; /* gutter size */
background-clip: padding-box; background-clip: padding-box;
& > div { & > div {
/* change div to reference your elements you put in <Masonry> */ /* change div to reference your elements you put in <Masonry> */
@@ -244,6 +244,11 @@ $border-color: red;
} }
} }
// AirDC++ search results
.dupe-search-result {
background: lavender;
}
// Search // Search
.search { .search {
.main-search-bar { .main-search-bar {
@@ -363,6 +368,7 @@ $border-color: red;
margin-bottom: 20px; margin-bottom: 20px;
} }
//
// progress // progress
.progress-indicator-container { .progress-indicator-container {
height: 100%; height: 100%;

View File

@@ -57,6 +57,7 @@ export const AcquisitionPanel = (
dispatch( dispatch(
downloadAirDCPPItem(searchInstanceId, resultId, comicBookObjectId), downloadAirDCPPItem(searchInstanceId, resultId, comicBookObjectId),
); );
// this is to update the download count badge on the downloads tab
dispatch(getBundlesForComic(comicBookObjectId)); dispatch(getBundlesForComic(comicBookObjectId));
}, },
[dispatch], [dispatch],
@@ -128,7 +129,10 @@ export const AcquisitionPanel = (
<tbody> <tbody>
{map(airDCPPSearchResults, ({ result }, idx) => { {map(airDCPPSearchResults, ({ result }, idx) => {
return ( return (
<tr key={idx}> <tr
key={idx}
className={!isNil(result.dupe) ? "dupe-search-result" : ""}
>
<td> <td>
<p className="mb-2"> <p className="mb-2">
{result.type.id === "directory" ? ( {result.type.id === "directory" ? (
@@ -136,9 +140,13 @@ export const AcquisitionPanel = (
) : null}{" "} ) : null}{" "}
{ellipsize(result.name, 70)} {ellipsize(result.name, 70)}
</p> </p>
<dl> <dl>
<dd> <dd>
<div className="tags"> <div className="tags">
{!isNil(result.dupe) ? (
<span className="tag is-warning">Dupe</span>
) : null}
<span className="tag is-light is-info"> <span className="tag is-light is-info">
{result.users.user.nicks} {result.users.user.nicks}
</span> </span>
@@ -169,17 +177,17 @@ export const AcquisitionPanel = (
</div> </div>
</td> </td>
<td> <td>
<a <a
onClick={() => onClick={() =>
downloadDCPPResult( downloadDCPPResult(
searchInstance.id, searchInstance.id,
result.id, result.id,
props.comicBookMetadata._id, props.comicBookMetadata._id,
) )
} }
> >
<i className="fas fa-file-download"></i> <i className="fas fa-file-download"></i>
</a> </a>
</td> </td>
</tr> </tr>
); );

View File

@@ -1,9 +1,13 @@
import React, { useEffect, ReactElement } from "react"; import React, { useEffect, ReactElement } from "react";
import { getDownloadProgress } from "../actions/airdcpp.actions"; import {
getDownloadProgress,
getBundlesForComic,
} from "../actions/airdcpp.actions";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { RootState } from "threetwo-ui-typings"; import { RootState } from "threetwo-ui-typings";
import { isNil, map } from "lodash"; import { isNil, map } from "lodash";
import prettyBytes from "pretty-bytes"; import prettyBytes from "pretty-bytes";
import ellipsize from "ellipsize";
interface IDownloadsPanelProps { interface IDownloadsPanelProps {
data: any; data: any;
@@ -16,56 +20,87 @@ export const DownloadsPanel = (
const downloadProgressTick = useSelector( const downloadProgressTick = useSelector(
(state: RootState) => state.airdcpp.downloadProgressData, (state: RootState) => state.airdcpp.downloadProgressData,
); );
const bundles = useSelector((state: RootState) => {
return state.airdcpp.bundles;
});
console.log("BANDYA", bundles);
const dispatch = useDispatch(); const dispatch = useDispatch();
// useEffect(() => { useEffect(() => {
// dispatch(getDownloadProgress(props.data._id)); dispatch(getBundlesForComic(props.comicObjectId));
// }, [dispatch]); dispatch(getDownloadProgress(props.comicObjectId));
}, [dispatch]);
const ProgressTick = (props) => ( const ProgressTick = (props) => {
<div className="column is-one-quarter"> console.log("tick", props);
{JSON.stringify(props.downloadProgressTick)}
<progress
className="progress is-small is-success"
value={props.downloaded_bytes}
max={props.size}
>
{(parseInt(props.downloaded_bytes) / parseInt(props.size)) * 100}%
</progress>
<dl>
<dt>{props.name}</dt>
<dd>
{prettyBytes(props.downloaded_bytes)} of
{prettyBytes(props.size)}
</dd>
<dd>
{prettyBytes(props.speed)} per second. Time left:
{parseInt(props.seconds_left) / 60}
</dd>
<dd>{props.target}</dd>
</dl>
</div>
);
const Bundles = (props) => {
console.log(props)
return ( return (
<div> <div className="column is-half">
<dl> {JSON.stringify(props.data.downloadProgressTick)}
{!isNil(props.data) && <progress
props.data && className="progress is-small is-success"
map(props.data, (bundle) => ( value={props.data.downloaded_bytes}
<span key={bundle.id}> max={props.data.size}
<dt>{bundle.name}</dt> >
<dd>{bundle.target}</dd> {(parseInt(props.data.downloaded_bytes) / parseInt(props.data.size)) *
<dd>{bundle.size}</dd> 100}
</span> %
))} </progress>
</dl> <div className="card">
<div className="card-content is-size-7">
<dl>
<dt>{props.data.name}</dt>
<dd>
{prettyBytes(props.data.downloaded_bytes)} of{" "}
{prettyBytes(props.data.size)}
</dd>
<dd>{prettyBytes(props.data.speed)} per second.</dd>
<dd>
Time left:
{parseInt(props.data.seconds_left) / 60}
</dd>
<dd>{props.data.target}</dd>
</dl>
</div>
</div>
</div> </div>
); );
}; };
return !isNil(props.data) ? <Bundles data={props.data} /> : null; const Bundles = (props) => {
console.log(props);
return (
<table className="table is-striped">
<thead>
<tr>
<th>Filename</th>
<th>Size</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{!isNil(props.data) &&
props.data &&
map(props.data, (bundle) => (
<tr key={bundle.id}>
<td>
<h5>{ellipsize(bundle.name, 58)}</h5>
<span className="is-size-7">{bundle.target}</span>
</td>
<td>{prettyBytes(bundle.size)}</td>
</tr>
))}
</tbody>
</table>
);
};
return !isNil(props.data) ? (
<>
{!isNil(downloadProgressTick) ? (
<ProgressTick data={downloadProgressTick} />
) : null}
<Bundles data={bundles} />
</>
) : null;
}; };
export default DownloadsPanel; export default DownloadsPanel;

View File

@@ -32,7 +32,7 @@ export const VolumeGroups = (): ReactElement => {
map(volumeGroups.data, (group) => { map(volumeGroups.data, (group) => {
if (!isNil(group)) { if (!isNil(group)) {
return ( return (
<div className="stack"> <div className="stack" key={group.results.id}>
<img src={group.results.image.small_url} /> <img src={group.results.image.small_url} />
<div className="content"> <div className="content">
<div className="stack-title"> <div className="stack-title">

View File

@@ -3,6 +3,7 @@ import io, { Socket } from "socket.io-client";
import { SOCKET_BASE_URI } from "../../constants/endpoints"; import { SOCKET_BASE_URI } from "../../constants/endpoints";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { RMQ_SOCKET_CONNECTED } from "../../constants/action-types"; import { RMQ_SOCKET_CONNECTED } from "../../constants/action-types";
import { isNil } from "lodash";
const WebSocketContext = createContext(null); const WebSocketContext = createContext(null);
export const WebSocketProvider = ({ children }) => { export const WebSocketProvider = ({ children }) => {
@@ -10,7 +11,7 @@ export const WebSocketProvider = ({ children }) => {
let ws; let ws;
const dispatch = useDispatch(); const dispatch = useDispatch();
if (!socket) { if (!isNil(socket)) {
socket = io(SOCKET_BASE_URI); socket = io(SOCKET_BASE_URI);
socket.on("connect", () => { socket.on("connect", () => {

View File

@@ -10,6 +10,7 @@ import { async as FastGlob } from "@bluelovers/fast-glob/bluebird";
import { Entry, Feed } from "opds-extra/lib/v1"; import { Entry, Feed } from "opds-extra/lib/v1";
import { Link } from "opds-extra/lib/v1/core"; import { Link } from "opds-extra/lib/v1/core";
import router from "../router"; import router from "../router";
import xml2js from "xml2js";
const path_of_books = "/Users/rishi/work/threetwo/src/server/comics"; const path_of_books = "/Users/rishi/work/threetwo/src/server/comics";
router.use("/opds", async (req, res, next) => { router.use("/opds", async (req, res, next) => {
@@ -18,22 +19,17 @@ router.use("/opds", async (req, res, next) => {
title: `title`, title: `title`,
subtitle: `subtitle`, subtitle: `subtitle`,
icon: "/favicon.ico", icon: "/favicon.ico",
link: {
rel: EnumLinkRel.SELF,
href: "/opds-catalogs/root.xml",
type: "application/atom+xml;profile=opds-catalog;kind=navigation",
} as Link,
}), }),
[ [
async (feed: Feed) => { async (feed: Feed) => {
feed.id = "bastard12312899lfh-1238989123-1231289812"; feed.id = "urn:uuid:2853dacf-ed79-42f5-8e8a-a7bb3d1ae6a2";
feed.books = feed.books || []; feed.books = feed.books || [];
await FastGlob(["*.cbr", "*.cbz", "*.cb7", "*.cba", "*.cbt"], { await FastGlob(["*.cbr", "*.cbz", "*.cb7", "*.cba", "*.cbt"], {
cwd: path_of_books, cwd: path_of_books,
}).each((file, idx) => { }).each((file, idx) => {
const ext = extname(file); const ext = extname(file);
const title = basename(file, ext); const title = basename(file, ext);
const href = encodeURI(`/file/${file}`); const href = encodeURI(`/api/file/${file}`);
const type = lookup(ext) || "application/octet-stream"; const type = lookup(ext) || "application/octet-stream";
const entry = Entry.deserialize<Entry>({ const entry = Entry.deserialize<Entry>({
@@ -49,7 +45,6 @@ router.use("/opds", async (req, res, next) => {
}); });
if (!isUndefined(feed) && !isUndefined(feed.books)) { if (!isUndefined(feed) && !isUndefined(feed.books)) {
console.log("ENTRY", entry)
feed.books.push(entry); feed.books.push(entry);
} }
}); });
@@ -59,8 +54,35 @@ router.use("/opds", async (req, res, next) => {
], ],
).then((feed) => { ).then((feed) => {
res.setHeader("Content-Type", "application/xml"); res.setHeader("Content-Type", "application/xml");
console.log("FFFEEDD", feed); let data;
return res.end(feed.toXML()); xml2js.parseString(feed.toXML(), (err, result) => {
result.feed.link = {
$: {
rel: "self",
href: "/opds-catalogs/root.xml",
type: "application/atom+xml;profile=opds-catalog;kind=navigation",
},
_: "",
};
const builder = new xml2js.Builder({
xmldec: {
version: "1.0",
encoding: "UTF-8",
standalone: false,
},
});
data = builder.buildObject(result, {
renderOpts: {
pretty: true,
indent: " ",
newline: "\n",
allowEmpty: true,
},
});
// write data to file
console.log(data);
});
return res.end(data);
}); });
}); });

View File

@@ -11749,7 +11749,7 @@ sass-loader@^11.0.1:
klona "^2.0.4" klona "^2.0.4"
neo-async "^2.6.2" neo-async "^2.6.2"
sax@~1.2.4: sax@>=0.6.0, sax@~1.2.4:
version "1.2.4" version "1.2.4"
resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -14141,11 +14141,24 @@ xml-schema2@^3.0.1:
xml-parser "1.2.1" xml-parser "1.2.1"
xmlbuilder "15.1.1" xmlbuilder "15.1.1"
xml2js@^0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@15.1.1: xmlbuilder@15.1.1:
version "15.1.1" version "15.1.1"
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz"
integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlchars@^2.2.0: xmlchars@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz"